9

I am working on an open source project, Storj. I am writing a Java client which connects to a Node.js websocket backend. The client uses Tyrus. The communication should go as follows:

  • Connect
  • Client sends auth token (text).
  • Server sends a file back (binary).
  • Server closes connection.

I am having problems as my @OnMessage never gets called. I have tried with a simple javascript client online here to the same URL and with the same token: https://www.websocket.org/echo.html

I do get a response using this, which tells me something is wrong with the Java project.

Before being able to download the file, at an earlier stage, I am able to upload the file without any problems. However, that step does not require an @OnMessage to be called (It just uploads the file and then the server disconnects with a message), so I am not sure if my @OnMessage is ever working.

Here is the relevant code for the Websocket (Also available on Github): https://github.com/NutterzUK/storj-java-bridge-client/blob/master/storj-client/src/main/java/storj/io/client/websockets/WebsocketFileRetriever.java

package storj.io.client.websockets;

import com.google.gson.Gson;
import storj.io.restclient.model.FilePointer;

import javax.websocket.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;

/**
 * Created by steve on 12/07/2016.
 */
@ClientEndpoint
public class WebsocketFileRetriever {

    private Logger logger = Logger.getLogger(this.getClass().getName());
    private Gson gson = new Gson();
    private FilePointer filePointer;
    private File outputFile;
    private AuthorizationModel authModel;
    private CountDownLatch latch;

    public WebsocketFileRetriever(FilePointer filePointer, File outputFile, CountDownLatch latch){
        this.filePointer = filePointer;
        this.latch = latch;
        this.outputFile = outputFile;
        authModel = new AuthorizationModel();
        authModel.setToken(filePointer.getToken());
        authModel.setOperation(filePointer.getOperation());
        authModel.setHash(filePointer.getHash());
    }

    @OnMessage
    public void onMessage(String s){
        logger.info("Received ... " + s);
    }

    @OnMessage
    public void onMessage(ByteBuffer message, Session session) {
        logger.info("Received ...." + message);
    }

    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        logger.info("Opened");
        try {
            session.getBasicRemote().sendText(gson.toJson(authModel), true);
        } catch (IOException e) {
            e.printStackTrace();
        }

        logger.info("sent: " + gson.toJson(authModel));
    }

    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        logger.info("Closed Websocket: " + closeReason.getCloseCode() + " " + closeReason.getReasonPhrase());
        //latch.countDown();
    }

    @OnError
    public void onError(Session session, Throwable t) {
        t.printStackTrace();
    }
}

And the code which kicks off this websocket, available here https://github.com/NutterzUK/storj-java-bridge-client/blob/master/storj-client/src/main/java/storj/io/client/DefaultStorjClient.java :

        CountDownLatch latch;
        latch = new CountDownLatch(1);
        ClientManager wsClient = ClientManager.createClient();
        try {
            wsClient.setDefaultMaxBinaryMessageBufferSize(Integer.MAX_VALUE);
            wsClient.setDefaultMaxTextMessageBufferSize(Integer.MAX_VALUE);
            logger.info("CONNECTING TO: " + "ws://" + pointer.getFarmer().getAddress() + ":" + pointer.getFarmer().getPort());
            final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build();

            wsClient.connectToServer(new WebsocketFileRetriever(pointer, encryptedOutputFile, latch), cec, new URI("ws://" + pointer.getFarmer().getAddress() + ":" + pointer.getFarmer().getPort()));
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

I have also tried upgrading Tyrus to the latest version, and I get the same result. Any ideas?

The output of this code is:

    Aug 25, 2016 8:55:31 PM storj.io.client.DefaultStorjClient downloadFile
INFO: CONNECTING TO: ws://164.storj.eu:8607
Aug 25, 2016 8:55:35 PM storj.io.client.websockets.WebsocketFileRetriever onOpen
INFO: Opened
Aug 25, 2016 8:55:35 PM storj.io.client.websockets.WebsocketFileRetriever onOpen
INFO: sent: {"token":"06c36d4bac4f07ee1751068b5b2230f22e884b38","hash":"837b79bec927a1d8fa7fedd2ea0bb276e0d86e0f","operation":"PULL"}
Aug 25, 2016 8:56:11 PM storj.io.client.websockets.WebsocketFileRetriever onClose
INFO: Closed Websocket: NORMAL_CLOSURE Closing

After sending the message, it hangs for a while before the "NORMAL_CLOSURE" message from the @OnClose.

Update: A really easy way to run this to reproduce the issue

I've added a test username and password to the git repository, so the code available is here: https://github.com/NutterzUK/storj-java-bridge-client

To run it, you just need to run storj.io.client.main.MainTest

A quick run through of what it does. It'll first send some HTTP requests to get a token. It'll use that token to connect to someone's machine via a websocket, and it'll send that token as text. In response, it should receive a file as bytes.

Before it connects, it prints out the token and the address it is about to connect to. It will hang for a bit before being closed and no onMessage method is ever called. For testing, if you put a System.exit in there (Uncomment Line 152 in DefaultStorjClient.java), it'll not connect so you can use that token in another client. I have tested using https://www.websocket.org/echo.html (Make sure that your browser will allow unsafe URLs as it's not "wss", to do this in Chrome you'll need to click the shield in the top right. I can see the server does respond: Image showing the blob being received

This shows that a blob is indeed sent in response to the text message, but the @OnMessage in Tyrus never gets fired.

ThePerson
  • 3,048
  • 8
  • 43
  • 69
  • The JS server sends this as a "blob", could this be related/or a problem? – ThePerson Aug 26 '16 at 20:24
  • It would be nice to have something testable. I mean.. I connected to the service, but I can't make it send me a file, so I cannot test whether Tyrus can receive a message from your server. Anyway, Tyrus works with echo server as well, so that is not really a useful test. (just point your tyrus client to "ws://echo.websocket.org" and send a message). – Pavel Bucek Aug 26 '16 at 22:46
  • Thanks. Yep, it does work with the echo server, but the echo one I don't think sends back bytes. I think if you make an account on storj.io (It's free), you can run the "testmain" and send a file to test it. It's all up on github, unfortunately I'm really stumped as to why this is happening, so i'm going to add a bounty to this question in 7 hours when I can. – ThePerson Aug 27 '16 at 12:43
  • I can actually make a test account I would think and can give the username/password here to save anyone else doing that. I'll do that and add it to the question, and i'll also update the TestMain to just download a single file to highlight the problem. – ThePerson Aug 27 '16 at 12:47
  • I've added "Update: A really easy way to run this to see the issue" to the description. It should be really easy to reproduce, i've also given the output from the echo test client which can call the same websocket server and does get a blob back. I'll add a bounty as soon as SO lets me, in 6 hours. – ThePerson Aug 27 '16 at 13:23

1 Answers1

2

In the end I switched to TallNate, and this problem doesn't exist.

I found after timing it that it always disconnected me after 30 seconds. Usually responses are quicker than in 30s, so I am not sure why it was hanging then disconnecting. I tried setting the timeout in Tyrus but still it disconnected on the 30s mark. In the end I tried TallNate to see if I could set the timeout there... and out of the box it just worked.

https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts

ThePerson
  • 3,048
  • 8
  • 43
  • 69
  • have you found the actual reason? Maybe the other client just sends periodic pings (which is supported in Tyrus as well, see https://tyrus.java.net/apidocs/1.13/org/glassfish/tyrus/core/TyrusSession.html#setHeartbeatInterval(long) – Pavel Bucek Sep 03 '16 at 19:28