WebSocket & WAMP in Java for Android and Java 8

Overview

Autobahn|Java

Client library providing WAMP on Java 8 (Netty) and Android, plus (secure) WebSocket for Android.

Docker Hub Travis Docs


Autobahn|Java is a subproject of the Autobahn project and provides open-source client implementations for

running on Android and Netty/Java8/JVM.

The WebSocket layer is using a callback based user API, and is specifically written for Android. Eg it does not run any network stuff on the main (UI) thread.

The WAMP layer is using Java 8 CompletableFuture for WAMP actions (call, register, publish and subscribe) and the Observer pattern for WAMP session, subscription and registration lifecycle events.

The library is MIT licensed, maintained by the Crossbar.io Project, tested using the AutobahnTestsuite and published as a JAR to Maven and as a Docker toolchain image to Dockerhub.


Download

Grab via Maven:

<dependency>
    <groupId>io.crossbar.autobahn</groupId>
    <artifactId>autobahn-android</artifactId>
    <version>20.7.1</version>
</dependency>

Gradle:

dependencies {
    implementation 'io.crossbar.autobahn:autobahn-android:20.7.1'
}

For non-android systems use artifactID autobahn-java or just Download the latest JAR

Getting Started

The demo clients are easy to run, you only need make and docker installed to get things rolling.

$ make crossbar # Starts crossbar in a docker container
$ make python # Starts a python based WAMP components that provides calls for the Java demo client

and finally

$ make java # Starts the java (Netty) based demo client that performs WAMP actions

Show me some code

The code in demo-gallery contains some examples on how to use the autobahn library, it also contains convenience methods to use. Below is a basic set of code examples showing all 4 WAMP actions.

Subscribe to a topic

public void demonstrateSubscribe(Session session, SessionDetails details) {
    // Subscribe to topic to receive its events.
    CompletableFuture<Subscription> subFuture = session.subscribe("com.myapp.hello",
            this::onEvent);
    subFuture.whenComplete((subscription, throwable) -> {
        if (throwable == null) {
            // We have successfully subscribed.
            System.out.println("Subscribed to topic " + subscription.topic);
        } else {
            // Something went bad.
            throwable.printStackTrace();
        }
    });
}

private void onEvent(List<Object> args, Map<String, Object> kwargs, EventDetails details) {
    System.out.println(String.format("Got event: %s", args.get(0)));
}

Since we are only accessing args in onEvent(), we could simplify it like:

private void onEvent(List<Object> args) {
    System.out.println(String.format("Got event: %s", args.get(0)));
}

Publish to a topic

public void demonstratePublish(Session session, SessionDetails details) {
    // Publish to a topic that takes a single arguments
    List<Object> args = Arrays.asList("Hello World!", 900, "UNIQUE");
    CompletableFuture<Publication> pubFuture = session.publish("com.myapp.hello", args);
    pubFuture.thenAccept(publication -> System.out.println("Published successfully"));
    // Shows we can separate out exception handling
    pubFuture.exceptionally(throwable -> {
        throwable.printStackTrace();
        return null;
    });
}

A simpler call would look like:

public void demonstratePublish(Session session, SessionDetails details) {
    CompletableFuture<Publication> pubFuture = session.publish("com.myapp.hello", "Hi!");
    ...
}

Register a procedure

public void demonstrateRegister(Session session, SessionDetails details) {
    // Register a procedure.
    CompletableFuture<Registration> regFuture = session.register("com.myapp.add2", this::add2);
    regFuture.thenAccept(registration ->
            System.out.println("Successfully registered procedure: " + registration.procedure));
}

private CompletableFuture<InvocationResult> add2(
        List<Object> args, Map<String, Object> kwargs, InvocationDetails details) {
    int res = (int) args.get(0) + (int) args.get(1);
    List<Object> arr = new ArrayList<>();
    arr.add(res);
    return CompletableFuture.completedFuture(new InvocationResult(arr));
}

A very precise add2 may look like:

private List<Object> add2(List<Integer> args, InvocationDetails details) {
    int res = args.get(0) + args.get(1);
    return Arrays.asList(res, details.session.getID(), "Java");
}

Call a procedure

public void demonstrateCall(Session session, SessionDetails details) {
    // Call a remote procedure.
    CompletableFuture<CallResult> callFuture = session.call("com.myapp.add2", 10, 20);
    callFuture.thenAccept(callResult ->
            System.out.println(String.format("Call result: %s", callResult.results.get(0))));
}

Calling procedure with variable data type parameters

public void demonstrateCall(Session session, SessionDetails details) {
    // Call a remote procedure.
    byte[] var1 = new byte[20];
    String var2 = "A sample text";
    int var3 = 99;
    List<Object> args = new ArrayList<>();
    args.add(var1);
    args.add(var2);
    args.add(var3);
    CompletableFuture<CallResult> callFuture = session.call("com.myapp.myproc", args);
    callFuture.thenAccept(callResult ->
            System.out.println(String.format("Call result: %s", callResult.results.get(0))));
}

Connecting the dots

public void main() {
    // Create a session object
    Session session = new Session();
    // Add all onJoin listeners
    session.addOnJoinListener(this::demonstrateSubscribe);
    session.addOnJoinListener(this::demonstratePublish);
    session.addOnJoinListener(this::demonstrateCall);
    session.addOnJoinListener(this::demonstrateRegister);

    // finally, provide everything to a Client and connect
    Client client = new Client(session, url, realm);
    CompletableFuture<ExitInfo> exitInfoCompletableFuture = client.connect();
}

Authentication

Authentication is simple, we just need to create an object of the desired authenticator and pass that to the Client

Ticket Auth

public void main() {
    ...
    IAuthenticator authenticator = new TicketAuth(authid, ticket);
    Client client = new Client(session, url, realm, authenticator);
    CompletableFuture<ExitInfo> exitInfoCompletableFuture = client.connect();
}

Challenge Response Auth

public void main() {
    ...
    IAuthenticator authenticator = new ChallengeResponseAuth(authid, secret);
    Client client = new Client(session, url, realm, authenticator);
    CompletableFuture<ExitInfo> exitInfoCompletableFuture = client.connect();
}

Cryptosign Auth

public void main() {
    ...
    IAuthenticator authenticator = new CryptosignAuth(authid, privkey, pubkey);
    Client client = new Client(session, url, realm, authenticator);
    CompletableFuture<ExitInfo> exitInfoCompletableFuture = client.connect();
}

You can also provide a list of Authenticators

public void main() {
    ...
    List<IAuthenticator> authenticators = new ArrayList<>();
    authenticators.add(new TicketAuth(authid, ticket));
    authenticators.add(new CryptosignAuth(authid, privkey, pubkey));
    Client client = new Client(session, url, realm, authenticators);
    CompletableFuture<ExitInfo> exitInfoCompletableFuture = client.connect();
}

Autobahn also supports POJOs

Here is how to call a remote procedure that returns a list of Person POJOs

// Call a remote procedure that returns a Person with id 1
CompletableFuture<Person> callFuture = mSession.call("com.example.get_person", 1);
callFuture.whenCompleteAsync((person, throwable) -> {
    if (throwable != null) {
        // handle error
    } else {
        // success!
        // do something with person
    }
}, mExecutor);
// call a remote procedure that returns a List<Person>
CompletableFuture<List<Person>> callFuture = mSession.call(
        // remote procedure to call
        "com.example.get_persons_by_department",

        // positional call arguments
        new ArrayList<Object>() {List.of("department-7")},

        // call return type
        new TypeReference<List<Person>>() {}
);

callFuture.whenCompleteAsync((persons, throwable) -> {
    if (throwable != null) {
        // handle error
    } else {
        // success!
        for (Person person: persons) {
            // do something with person
        }
    }
}, mExecutor);

Also register a procedure that returns a Person

private Person get_person() {
    return new Person("john", "doe", "hr");
}

private void main() {
    CompletableFuture<Registration> regFuture = session.register(
            "io.crossbar.example.get_person", this::get_person);
    regFuture.whenComplete((registration, throwable) -> {
        System.out.println(String.format(
                "Registered procedure %s", registration.procedure));
    });
}

WebSocket on Android

Echo example

WebSocketConnection connection = new WebSocketConnection();
connection.connect("wss://echo.websocket.org", new WebSocketConnectionHandler() {
    @Override
    public void onConnect(ConnectionResponse response) {
        System.out.println("Connected to server");
    }

    @Override
    public void onOpen() {
        connection.sendMessage("Echo with Autobahn");
    }

    @Override
    public void onClose(int code, String reason) {
        System.out.println("Connection closed");
    }

    @Override
    public void onMessage(String payload) {
        System.out.println("Received message: " + payload);
        connection.sendMessage(payload);
    }
});

Building from source

Building Autobahn is pretty simple

Android build

For Android, we recommend to use Android Studio. Just import the project in Android Studio, it will tell you if there are any missing dependencies, install them and then just build the project from Build > Rebuild Project and you will have the aar artifact in autobahn/build/outputs/aar/

Netty build

To produce a build for non-android systems make sure you have docker and make installed then just use run below command on the root directory of the project

make build_autobahn

and that will output the jar file in autobahn/build/libs/.

Get in touch

Get in touch by joining our forum.


Version 1

Version 1 of this library is still in the repo here, but is no longer maintained.

Version 1 only supported non-secure WebSocket on Android and only supported WAMP v1.

Both of these issues are fixed in the (current) version of Autobahn|Java.


Comments
  • Added TLS support for WebSocketConnection

    Added TLS support for WebSocketConnection

    Replaced nio's API(SocketChannel) with Socket and added SSL support using SSLSocket. I have tried doing the replacement with minimal changes to the code. Please let me know, If there are any issues.

    P.V.S.

    opened by pvsundarram 34
  • Publish on Maven

    Publish on Maven

    To get the AutobahnAndroid artifact into the maven central repository you should use the facilities offered by oss.sonatype.org. https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide

    That should help you understand how to get the artifact and all associated metadata up to oss.sonatype.org, once you are happy with it, you can request that your artifacts be synced to Maven Central http://repo.maven.org/

    FYI, sonatype.org is run by many Apache Maven committer and PMC members, they do a great job in helping out the open source community.

    opened by oberstet 28
  • Exception: sending message to a Handler on a dead thread

    Exception: sending message to a Handler on a dead thread

    10-07 10:53:27.659: W/MessageQueue(27516): Handler (de.tavendo.autobahn.WebSocketWriter) {42dbc630} sending message to a Handler on a dead thread 10-07 10:53:27.659: W/MessageQueue(27516): java.lang.RuntimeException: Handler (de.tavendo.autobahn.WebSocketWriter) {42dbc630} sending message to a Handler on a dead thread 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:320) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Handler.enqueueMessage(Handler.java:626) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Handler.sendMessageAtTime(Handler.java:595) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Handler.sendMessageDelayed(Handler.java:566) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Handler.sendMessage(Handler.java:503) 10-07 10:53:27.659: W/MessageQueue(27516): at de.tavendo.autobahn.WebSocketWriter.forward(WebSocketWriter.java:102) 10-07 10:53:27.659: W/MessageQueue(27516): at de.tavendo.autobahn.WebSocketConnection.failConnection(WebSocketConnection.java:181) 10-07 10:53:27.659: W/MessageQueue(27516): at de.tavendo.autobahn.WebSocketConnection.access$1100(WebSocketConnection.java:34) 10-07 10:53:27.659: W/MessageQueue(27516): at de.tavendo.autobahn.WebSocketConnection$2.handleMessage(WebSocketConnection.java:453) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Handler.dispatchMessage(Handler.java:102) 10-07 10:53:27.659: W/MessageQueue(27516): at android.os.Looper.loop(Looper.java:157) 10-07 10:53:27.659: W/MessageQueue(27516): at android.app.ActivityThread.main(ActivityThread.java:5356) 10-07 10:53:27.659: W/MessageQueue(27516): at java.lang.reflect.Method.invokeNative(Native Method) 10-07 10:53:27.659: W/MessageQueue(27516): at java.lang.reflect.Method.invoke(Method.java:515) 10-07 10:53:27.659: W/MessageQueue(27516): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265) 10-07 10:53:27.659: W/MessageQueue(27516): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081) 10-07 10:53:27.659: W/MessageQueue(27516): at dalvik.system.NativeStart.main(Native Method)

    bug needs-investigation WebSocket 
    opened by CrisVeli 15
  • Current review

    Current review

    This is a meta issue to collect some remarks to the current state of the implementation.

    CC @om26er

    • [x] on-ready
    • [x] request IDs
    • [x] session state
    • [x] call implementation. part 1
    • [x] call implementation. part 2
    • [x] completable future usage
    • [x] state machine transitions and session lifecycle

    on-ready

    When the session receives a WELCOME message, it correctly notifies all "on-join" listeners, and the user code for each of the listeners will run (from here):

    mOnJoinListeners.forEach(onJoinListener -> onJoinListener.onJoin(details));
    

    However, these notification handlers may return futures (or regular results), and we want to collect all these and run some code when all the returned futures have triggered.

    The code that we want to run is notifying all "on-ready" listeners and run the user code attached to these.

    opened by oberstet 13
  • Session class is not thread-safe by design

    Session class is not thread-safe by design

    In Session.onConnect is the following code:

    runAsync(() -> {
        for (OnConnectListener listener: mOnConnectListeners) {
            listener.onConnect(this);
        }
    }, getExecutor());
    

    Since Session is not currently thread safe, this is implies that the OnConnectListener is responsible for synchronizing on the Session object it is passed before making any calls on it. Was this intended or should the Session class be made thread safe?

    opened by fhriley 11
  • Release 18.3.1

    Release 18.3.1

    Released via https://github.com/crossbario/autobahn-java/pull/382

    The Docker toolchain image is published. Maven/gradle not .. as the Makefile requires tools on the host - rather than from inside a Docker image ..

    So we still need to publish to mvn/gradle @om26er

    opened by oberstet 10
  • Example of using POJOs with ABJ

    Example of using POJOs with ABJ

    Was "Add guides to Jackson/ObjectMapper"

    • http://www.baeldung.com/jackson-object-mapper-tutorial
    • https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/ObjectMapper.html
    WAMP enhancement 
    opened by oberstet 10
  • Document main usage idioms

    Document main usage idioms

    Example demonstrating usage with different types of args/kwargs in call and publish arguments, call returns and call errors, etc

    • Use of Jackson data-binding (to/from POJOs)
    • Use of JDK Lists and Maps over POJOs
    WAMP enhancement docs 
    opened by oberstet 10
  • Meta issue: AutobahnAndroid 2

    Meta issue: AutobahnAndroid 2

    This is a meta issue to organize the issues related to bringing AutobahnAndroid up-to-date with the current WAMP ecosystem.

    We'll be calling that next major release "AutobahnAndroid 2" for now.

    Situation

    AutobahnAndroid is a subproject of Autobahn.

    "The Autobahn project provides open-source implementations of the The WebSocket Protocol and The Web Application Messaging Protocol (WAMP) network protocols."

    AutobahnAndroid is a WebSocket and WAMP client library implementation specifically for Android. However, right now, it has multiple, concrete and important issues that need to get "fixed":

    1. upgrade to WAMP v2 (the current WAMP protocol version, which is a major revision of WAMP v1) - AutobahnAndroid is still at WAMP v1 and incompatible with Crossbar.io and other WAMP routers
    2. support TLS (and hence secure WebSocket wss): rip out use of NIO. Network IO should be done on reader/writer background threads (that's already the case), and use blocking IO on these (not yet, it is using NIO - a major PITA with no gain)
    3. Ensure the library works for both UI apps and background services, eg don't resolve/connect TCP sockets on the main thread (network IO already happens on background reader/writer threads, but not DNS resolve and initial socket connect)
    4. Update to Android 4.1+ ("Jellybean") APIs (everything before Android 4.1 will be unsupported)
    5. Update to latest Jackson (JSON ser/unser)
    6. support active sending of WebSocket Pings and client initiated automatic WebSocket Ping/Pong with timeouts for fast detection of TCP connection loss
    7. support automatic reconnection at the WebSocket and WAMP level

    Further, these would be nice, but can come later

    • support WAMP-CRA authentication
    • support WAMP-cryptosign authentication
    • support different WAMP serializers: not only JSON, but also CBOR via https://github.com/FasterXML/jackson-dataformat-cbor
    • support WebSocket compression ("permessage-deflate")

    We should also

    • Change license from Apache to MIT

    For testing/examples:

    • The library must be tested at the WebSocket level with http://autobahn.ws/testsuite/ and everything (other than optional things like WebSocket compression) MUST pass green (strict!)
    • The library must be test at the WAMP level against Crossbar.io: http://crossbar.io
    • We need (to update) the following test programs (written using the revised library):
      • a simple WebSocket usage example
      • a simple WAMP usage example ("Hello WAMP" .. like we have for other Autobahn languages .. which demonstrates all 4 WAMP interactions (call/register/subscribe/publish)
      • a AutobahnTestsuite client app (for testing the library)

    Next Steps

    The API as defined here and here needs to be revised.

    There should be a separate issue for this, as it needs discussion and close collaboration.

    WebSocket WAMP 
    opened by oberstet 10
  • Added the option to pass the secret to wampcra as byte array.

    Added the option to pass the secret to wampcra as byte array.

    The main goal is to allow secrets with different encodings and not only UTF-8. The modification doesn't break the current string secret just add also the byte array as an option.

    opened by bluecatpixel 9
  • My socket is connected and want to receive message continuously?

    My socket is connected and want to receive message continuously?

    I can receive a message when I connect to the socket. Once I connected to the socket, my server responds whenever something happens so how will I receive a message continuously.

    opened by sovoereign 9
  • Closing socket on main thread can throw exception on Android 11

    Closing socket on main thread can throw exception on Android 11

    I am seeing NetworkOnMainThreadException when handling close event

    10-28 17:12:53.003 20108 20108 E AndroidRuntime: android.os.NetworkOnMainThreadException 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1605) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:426) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.ConscryptEngineSocket$SSLOutputStream.writeInternal(ConscryptEngineSocket.java:657) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.ConscryptEngineSocket$SSLOutputStream.access$200(ConscryptEngineSocket.java:616) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.ConscryptEngineSocket.drainOutgoingQueue(ConscryptEngineSocket.java:585) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.ConscryptEngineSocket.close(ConscryptEngineSocket.java:480) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.close(ConscryptEngineSocket.java:748) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at java.io.BufferedInputStream.close(BufferedInputStream.java:485) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.WebSocketReader.quit(WebSocketReader.java:130) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.WebSocketConnection.closeReaderThread(WebSocketConnection.java:259) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.WebSocketConnection.failConnection(WebSocketConnection.java:300) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.WebSocketConnection.access$2700(WebSocketConnection.java:60) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.WebSocketConnection$2.onMessage(WebSocketConnection.java:712) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at io.crossbar.autobahn.websocket.utils.AndroidThreadMessenger$1.handleMessage(AndroidThreadMessenger.java:38) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at android.os.Looper.loop(Looper.java:246) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8653) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) 10-28 17:12:53.003 20108 20108 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

    I found this commit in AOSP Android 11 branch that I believe is the root cause of this: https://cs.android.com/android/_/android/platform/external/conscrypt/+/9a16085cc2f182bfca9dd6a0d02c82c934887a82

    opened by novak-dev 1
  • Set max message payload size from Client class

    Set max message payload size from Client class

    From what I understand, the field mMaxMessagePayloadSize of WebSocketOptions is used for the maximum size of a message to be sent, while the field mMaxFramePayloadSize is the maximum size of a fragment of the message, in case the message need to be splitted. Is that correct?

    If yes, I would like to be able to set mMaxMessagePayloadSize to 16MB and mMaxFramePayloadSize to something much smaller, like 128KB.

    But from what I see, when calling the connect() method of the Client class: image I can only pass the mMaxFramePayloadSize in the TransportOptions.

    Is there a reason why we cannot pass the mMaxMessagePayloadSize as well?

    Am I missing something?

    needs-discussion WAMP enhancement 
    opened by ampeixoto 6
  • Add docs rgd threads and thread-safety

    Add docs rgd threads and thread-safety

    I detected an issue when trying to make multiple calls concurrently.

    Sometimes, if I make 4 concurrent calls to Session.call() I would only get 3 responses and worse, sometimes one of the responses was the payload of another procedure...

    So I dig into the autobahn-java code and noticed the following: In Session.java you have this:

        private <T> CompletableFuture<T> reallyCall(
                String procedure,
                List<Object> args, Map<String, Object> kwargs,
                CallOptions options,
                TypeReference<T> resultTypeReference,
                Class<T> resultTypeClass) {
            throwIfNotConnected();
    
            CompletableFuture<T> future = new CompletableFuture<>();
    
            long requestID = mIDGenerator.next();
    
            mCallRequests.put(requestID, new CallRequest(requestID, procedure, future, options,
                    resultTypeReference, resultTypeClass));
    
            if (options == null) {
                send(new Call(requestID, procedure, args, kwargs, 0));
            } else {
                send(new Call(requestID, procedure, args, kwargs, options.timeout));
            }
            return future;
        }
    

    And in IDGenerator.java this:

    public class IDGenerator {
        private long mNext;
    
        public long next() {
            mNext += 1;
            if (mNext > 9007199254740992L) {
                mNext = 1;
            }
            return mNext;
        }
    }
    

    As you can see, that is not thread-safe. Neither mNext nor mCallRequests can be set concurrently.

    And to prove it, I created a small snippet in kotlin:

    fun main() = runBlocking {
    
        val scope = CoroutineScope(Job() + Dispatchers.IO)
        println("Start generating ids")
        val results = (1..50).map {
            generateIdsConcurrently(scope)
        }
        println("Results: $results")
        println("All successful: ${results.all { it }}")
    }
    
    private suspend fun generateIdsConcurrently(scope: CoroutineScope): Boolean {
        val tasks = mutableListOf<Job>()
        val idsMap = HashMap<Int, Int>()
        val numberOfIdsExpected = 10
        val idGenerator = IDGenerator()
        (1..numberOfIdsExpected).onEach { index ->
            val childJob = scope.launch {
                //this delay forces more failures
                delay(100)
                val id = idGenerator.next()
                idsMap[id.toInt()] = index
            }
            tasks.add(childJob)
        }
    
        tasks.joinAll()
    
        val expectedIds = idsMap.values.sorted()
        val generatedIds = idsMap.keys.sorted()
        return expectedIds == generatedIds
    }
    

    If we run this code, we can see that it almost always fails (created 50 trials to make it more frequent). So the generated IDs aren't always sequential.

    Similar issues happens to Session.subscribe() also (and potentially other methods).

    SOLUTION

    • First Step

    Make the next() method synchronized:

    public class IDGenerator {
        private long mNext;
    
        public synchronized long next() {
            mNext += 1;
            if (mNext > 9007199254740992L) {
                mNext = 1;
            }
            return mNext;
        }
    }
    

    This improved quite a lot but it was still failling sometimes.

    • Second Step

    Replace the HashMap by a ConcurrentHashMap. With this, the test passes 100% of the time.

    QUESTIONS

    • Is my analysis correct or I am making some mistake?
    • Is there any hidden reason for why this is not thread safe?
    • Was this already detected before? I didn't find anything about it...
    • Is the caller of autobahn expected to externally synchronize the calls for some reason?
    bug docs 
    opened by ampeixoto 6
  • Support connection through SOCKS5

    Support connection through SOCKS5

    Autobahn Java should be able to connect to crossbar over SOCKS5. Our Java websocket implementation internally uses the Java Socket class, which supports passing a Proxy object.

    I have briefly played with it and it seems I was able to connect to Crossbar using SOCKS5 proxy from autobahn-java with a few lines of code.

    If you @oberstet agree, we can add initial support to our websocket implementation.

    The user API would only need to provide an instance of a Proxy object (that we define), which supports 4 paramets

    • socks5 server address
    • socks5 port
    • socks5 username
    • socks5 password

    The last two can be optional if a proxy supports anonymous connection.

    WebSocket enhancement 
    opened by om26er 2
  • Feature request: support for Proguard

    Feature request: support for Proguard

    It would be nice if the library provided Proguard rules.

    Right now, we have to disabled proguard completely for the package to make sure it continues working:

    -keep class io.crossbar.** { *; }
    
    enhancement CI-CD 
    opened by jschmid 1
  • Autobahn does not comply with Android Strict Mode

    Autobahn does not comply with Android Strict Mode

    If you try to enable StrictMode, specifically untagged sockets, the app will trigger warnings (or crash if it is configured as such) because of the socket opened by Autobahn.

    This is a bit annoying because this prevents us to use the "catch all" detectAll() method from StrictMode.

    From what I understand, WebSocketConnector should call TrafficStats.setThreadStatsTag(int) to use a TAG for the socket created by Autobahn.

    opened by jschmid 0
Owner
Crossbar.io
Seamless connectivity for the IoT and real-time microservices
Crossbar.io
General purpose utilities and hash functions for Android and Java (aka java-common)

Essentials Essentials are a collection of general-purpose classes we found useful in many occasions. Beats standard Java API performance, e.g. LongHas

Markus Junginger 1.4k Dec 29, 2022
General purpose utilities and hash functions for Android and Java (aka java-common)

Essentials Essentials are a collection of general-purpose classes we found useful in many occasions. Beats standard Java API performance, e.g. LongHas

Markus Junginger 1.4k Dec 29, 2022
gRPC and protocol buffers for Android, Kotlin, and Java.

Wire “A man got to have a code!” - Omar Little See the project website for documentation and APIs. As our teams and programs grow, the variety and vol

Square 3.9k Dec 31, 2022
Trail is a simple logging system for Java and Android. Create logs using the same API and the library will detect automatically in which platform the code is running.

Trail Trail is a simple logging system for Java and Android. Create logs using the same API and the library will detect automatically in which platfor

Mauricio Togneri 13 Aug 29, 2022
a simple cache for android and java

ASimpleCache ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。 1、它可以缓存什么东西? 普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 b

Michael Yang 3.7k Dec 14, 2022
A lightning fast, transactional, file-based FIFO for Android and Java.

Tape by Square, Inc. Tape is a collection of queue-related classes for Android and Java. QueueFile is a lightning-fast, transactional, file-based FIFO

Square 2.4k Dec 30, 2022
UPnP/DLNA library for Java and Android

Cling EOL: This project is no longer actively maintained, code may be outdated. If you are interested in maintaining and developing this project, comm

4th Line 1.6k Jan 4, 2023
Error handling library for Android and Java

ErrorHandler Error handling library for Android and Java Encapsulate error handling logic into objects that adhere to configurable defaults. Then pass

null 237 Dec 29, 2022
Multiplaform kotlin library for calculating text differences. Based on java-diff-utils, supports JVM, JS and native targets.

kotlin-multiplatform-diff This is a port of java-diff-utils to kotlin with multiplatform support. All credit for the implementation goes to original a

Peter Trifanov 51 Jan 3, 2023
BinGait is a tool to disassemble and view java class files, developed by BinClub.

BinGait Tool to diassemble java class files created by x4e. Usage To run BinGait, run java -jar target/bingait-shadow.jar and BinGait will launch. If

null 18 Jul 7, 2022
Java implementation of a Disk-based LRU cache which specifically targets Android compatibility.

Disk LRU Cache A cache that uses a bounded amount of space on a filesystem. Each cache entry has a string key and a fixed number of values. Each key m

Jake Wharton 5.7k Dec 31, 2022
A low intrusive, configurable android library that converts layout XML files into Java code to improve performance

qxml English 一个低侵入,可配置的 Android 库,用于将 layout xml 文件转换为 Java 代码以提高性能。 与X2C的对比 X2C: 使用注解处理器生成View类,使用时需要在类中添加注解,并替换setContentView方法,侵入性较强; 对于布局属性的支持不够完美

null 74 Oct 6, 2022
Runtime code generation for the Java virtual machine.

Byte Buddy runtime code generation for the Java virtual machine Byte Buddy is a code generation and manipulation library for creating and modifying Ja

Rafael Winterhalter 5.3k Jan 7, 2023
Apk parser for java

APK parser lib, for decoding binary XML files, getting APK meta info. Table of Contents Features Get APK-parser Usage 1. APK Info 2. Get Binary XML an

Hsiafan 1.1k Jan 2, 2023
Apk parser for java

APK parser lib, for decoding binary XML files, getting APK meta info. Table of Contents Features Get APK-parser Usage 1. APK Info 2. Get Binary XML an

Hsiafan 1.1k Dec 18, 2022
java.io.File compatible SAF library

DocumentFileX java.io.File compatible SAF implementation Tired of SAF bullshits? Implement SAF with ease! This library is in alpha stage. Most feature

null 24 Aug 25, 2022
MMDUtils is a library for read/write mmd related file in java

MMDUtils MMDUtils is a library for read/write mmd related file in java Features Read/Write VMD(Vocaloid Motion Data) file Read/Write PMX(Polygon Model

null 5 Jan 28, 2022
Access and process various types of personal data in Android with a set of easy, uniform, and privacy-friendly APIs.

PrivacyStreams PrivacyStreams is an Android library for easy and privacy-friendly personal data access and processing. It offers a functional programm

null 269 Dec 1, 2022
A simple and easy to use stopwatch and timer library for android

TimeIt Now with Timer support! A simple and easy to use stopwatch and timer library for android Introduction A stopwatch can be a very important widge

Yashovardhan Dhanania 35 Dec 10, 2022