Adding the HubProtocol Layer to the Java Client (#2524)
This commit is contained in:
parent
d318d733c4
commit
b0ce55e6c8
|
|
@ -1,7 +1,15 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
public class DefaultJsonProtocolHandShakeMessage {
|
public class DefaultJsonProtocolHandShakeMessage {
|
||||||
String protocol = "json";
|
private String protocol = "json";
|
||||||
int version = 1;
|
private int version = 1;
|
||||||
|
private static final String RECORD_SEPARATOR = "\u001e";
|
||||||
|
|
||||||
|
public String createHandshakeMessage() {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.toJson(this) + RECORD_SEPARATOR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,31 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class HubConnection {
|
public class HubConnection {
|
||||||
private String _url;
|
private String _url;
|
||||||
private ITransport _transport;
|
private Transport _transport;
|
||||||
private OnReceiveCallBack callback;
|
private OnReceiveCallBack callback;
|
||||||
private HashMap<String, Action> handlers = new HashMap<>();
|
private HashMap<String, Action> handlers = new HashMap<>();
|
||||||
private JsonParser jsonParser = new JsonParser();
|
private HubProtocol protocol;
|
||||||
private static final String RECORD_SEPARATOR = "\u001e";
|
|
||||||
|
|
||||||
public Boolean connected = false;
|
public Boolean connected = false;
|
||||||
|
|
||||||
public HubConnection(String url) {
|
public HubConnection(String url) {
|
||||||
_url = url;
|
_url = url;
|
||||||
|
protocol = new JsonHubProtocol();
|
||||||
callback = (payload) -> {
|
callback = (payload) -> {
|
||||||
String[] messages = payload.split(RECORD_SEPARATOR);
|
|
||||||
|
|
||||||
for (String splitMessage : messages) {
|
InvocationMessage[] messages = protocol.parseMessages(payload);
|
||||||
|
|
||||||
// Empty handshake response "{}". We can ignore it
|
// message will be null if we receive any message other than an invocation.
|
||||||
if (splitMessage.length() == 2) {
|
// Adding this to avoid getting error messages on pings for now.
|
||||||
continue;
|
for (InvocationMessage message : messages) {
|
||||||
|
if (message != null && handlers.containsKey(message.target)) {
|
||||||
|
handlers.get(message.target).invoke(message.arguments[0]);
|
||||||
}
|
}
|
||||||
processMessage(splitMessage);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -39,44 +36,6 @@ public class HubConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processMessage(String message) {
|
|
||||||
JsonObject jsonMessage = jsonParser.parse(message).getAsJsonObject();
|
|
||||||
String messageType = jsonMessage.get("type").toString();
|
|
||||||
switch(messageType) {
|
|
||||||
case "1":
|
|
||||||
//Invocation Message
|
|
||||||
String target = jsonMessage.get("target").getAsString();
|
|
||||||
if (handlers.containsKey(target)) {
|
|
||||||
handlers.get(target).invoke(jsonMessage.get("arguments"));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
//Stream item
|
|
||||||
//Don't care yet
|
|
||||||
break;
|
|
||||||
case "3":
|
|
||||||
//Completion
|
|
||||||
//Don't care yet
|
|
||||||
break;
|
|
||||||
case "4":
|
|
||||||
//Stream invocation
|
|
||||||
//Don't care yet;
|
|
||||||
break;
|
|
||||||
case "5":
|
|
||||||
//Cancel invocation
|
|
||||||
//Don't care yet
|
|
||||||
break;
|
|
||||||
case "6":
|
|
||||||
//Ping
|
|
||||||
//Don't care yet
|
|
||||||
break;
|
|
||||||
case "7":
|
|
||||||
// Close message
|
|
||||||
//Don't care yet;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() throws InterruptedException {
|
public void start() throws InterruptedException {
|
||||||
_transport.setOnReceive(this.callback);
|
_transport.setOnReceive(this.callback);
|
||||||
_transport.start();
|
_transport.start();
|
||||||
|
|
@ -88,12 +47,13 @@ public class HubConnection {
|
||||||
connected = false;
|
connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(String method, Object arg1) {
|
public void send(String method, Object... args) {
|
||||||
InvocationMessage message = new InvocationMessage(method, new Object[]{ arg1 });
|
InvocationMessage invocationMessage = new InvocationMessage(method, args);
|
||||||
|
String message = protocol.writeMessage(invocationMessage);
|
||||||
_transport.send(message);
|
_transport.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void On(String target, Action callback) {
|
public void On(String target, Action callback) {
|
||||||
handlers.put(target, callback);
|
handlers.put(target, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
public interface HubProtocol {
|
||||||
|
String getName();
|
||||||
|
int getVersion();
|
||||||
|
TransferFormat getTransferFormat();
|
||||||
|
InvocationMessage[] parseMessages(String message);
|
||||||
|
String writeMessage(InvocationMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
public class InvocationMessage {
|
public class InvocationMessage {
|
||||||
private int type = 1;
|
int type = 1;
|
||||||
String invocationId;
|
String invocationId;
|
||||||
String target;
|
String target;
|
||||||
Object[] arguments;
|
Object[] arguments;
|
||||||
|
|
@ -28,7 +28,7 @@ public class InvocationMessage {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getArguments() {
|
public Object[] getArguments() {
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JsonHubProtocol implements HubProtocol {
|
||||||
|
private final JsonParser jsonParser = new JsonParser();
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
private static final String RECORD_SEPARATOR = "\u001e";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "json";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransferFormat getTransferFormat() {
|
||||||
|
return TransferFormat.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InvocationMessage[] parseMessages(String payload) {
|
||||||
|
String[] messages = payload.split(RECORD_SEPARATOR);
|
||||||
|
List<InvocationMessage> invocationMessages = new ArrayList<>();
|
||||||
|
for (String splitMessage : messages) {
|
||||||
|
// Empty handshake response "{}". We can ignore it
|
||||||
|
if (splitMessage.equals("{}")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject jsonMessage = jsonParser.parse(splitMessage).getAsJsonObject();
|
||||||
|
String messageType = jsonMessage.get("type").toString();
|
||||||
|
switch (messageType) {
|
||||||
|
case "1":
|
||||||
|
//Invocation Message
|
||||||
|
String target = jsonMessage.get("target").getAsString();
|
||||||
|
JsonElement args = jsonMessage.get("arguments");
|
||||||
|
invocationMessages.add(new InvocationMessage(target, new Object[] {args}));
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
//Stream item
|
||||||
|
//Don't care yet
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
//Completion
|
||||||
|
//Don't care yet
|
||||||
|
break;
|
||||||
|
case "4":
|
||||||
|
//Stream invocation
|
||||||
|
//Don't care yet;
|
||||||
|
break;
|
||||||
|
case "5":
|
||||||
|
//Cancel invocation
|
||||||
|
//Don't care yet
|
||||||
|
break;
|
||||||
|
case "6":
|
||||||
|
//Ping
|
||||||
|
//Don't care yet
|
||||||
|
break;
|
||||||
|
case "7":
|
||||||
|
// Close message
|
||||||
|
//Don't care yet;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invocationMessages.toArray(new InvocationMessage[invocationMessages.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String writeMessage(InvocationMessage invocationMessage) {
|
||||||
|
return gson.toJson(invocationMessage) + RECORD_SEPARATOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
public enum TransferFormat {
|
||||||
|
Text,
|
||||||
|
Binary
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
public interface ITransport {
|
public interface Transport {
|
||||||
void start() throws InterruptedException;
|
void start() throws InterruptedException;
|
||||||
void send(InvocationMessage invocationMessage);
|
void send(String message);
|
||||||
void setOnReceive(OnReceiveCallBack callback);
|
void setOnReceive(OnReceiveCallBack callback);
|
||||||
void onReceive(String message);
|
void onReceive(String message);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
@ -8,8 +8,7 @@ import org.java_websocket.handshake.ServerHandshake;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
public class WebSocketTransport implements ITransport {
|
public class WebSocketTransport implements Transport {
|
||||||
private static final String RECORD_SEPARATOR = "\u001e";
|
|
||||||
private WebSocketClient _webSocket;
|
private WebSocketClient _webSocket;
|
||||||
private OnReceiveCallBack onReceiveCallBack;
|
private OnReceiveCallBack onReceiveCallBack;
|
||||||
private URI _url;
|
private URI _url;
|
||||||
|
|
@ -23,13 +22,11 @@ public class WebSocketTransport implements ITransport {
|
||||||
@Override
|
@Override
|
||||||
public void start() throws InterruptedException {
|
public void start() throws InterruptedException {
|
||||||
_webSocket.connectBlocking();
|
_webSocket.connectBlocking();
|
||||||
_webSocket.send(createHandshakeMessage() + RECORD_SEPARATOR);
|
_webSocket.send((new DefaultJsonProtocolHandShakeMessage()).createHandshakeMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(InvocationMessage invocationMessage) {
|
public void send(String message) {
|
||||||
Gson gson = new Gson();
|
|
||||||
String message = gson.toJson(invocationMessage) + RECORD_SEPARATOR;
|
|
||||||
_webSocket.send(message);
|
_webSocket.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,11 +40,6 @@ public class WebSocketTransport implements ITransport {
|
||||||
this.onReceiveCallBack.invoke(message);
|
this.onReceiveCallBack.invoke(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createHandshakeMessage() {
|
|
||||||
Gson gson = new Gson();
|
|
||||||
return gson.toJson(new DefaultJsonProtocolHandShakeMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
_webSocket.closeConnection(0, "HubConnection Stopped");
|
_webSocket.closeConnection(0, "HubConnection Stopped");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class JsonHubProtocolTest {
|
||||||
|
private JsonHubProtocol jsonHubProtocol = new JsonHubProtocol();
|
||||||
|
private static final String RECORD_SEPARATOR = "\u001e";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkProtocolName() {
|
||||||
|
assertEquals("json", jsonHubProtocol.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkVersionNumber() {
|
||||||
|
assertEquals(1, jsonHubProtocol.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkTransferFormat() {
|
||||||
|
assertEquals(TransferFormat.Text, jsonHubProtocol.getTransferFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void VerifyWriteMessage() {
|
||||||
|
InvocationMessage invocationMessage = new InvocationMessage("test", new Object[] {"42"});
|
||||||
|
String result = jsonHubProtocol.writeMessage(invocationMessage);
|
||||||
|
String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E";
|
||||||
|
assertEquals(expectedResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ParseSingleMessage() {
|
||||||
|
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
|
||||||
|
InvocationMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
|
||||||
|
|
||||||
|
//We know it's only one message
|
||||||
|
assertEquals(1, messages.length);
|
||||||
|
InvocationMessage message = messages[0];
|
||||||
|
assertEquals("test", message.target);
|
||||||
|
assertEquals(null, message.invocationId);
|
||||||
|
assertEquals(1, message.type);
|
||||||
|
JsonArray messageResult = (JsonArray) message.arguments[0];
|
||||||
|
assertEquals(42, messageResult.getAsInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ParseHandshakeResponsePlusMessage() {
|
||||||
|
String twoMessages = "{}\u001E{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
|
||||||
|
InvocationMessage[] messages = jsonHubProtocol.parseMessages(twoMessages);
|
||||||
|
|
||||||
|
//We ignore the Handshake response for now
|
||||||
|
InvocationMessage message = messages[0];
|
||||||
|
assertEquals("test", message.target);
|
||||||
|
assertEquals(null, message.invocationId);
|
||||||
|
assertEquals(1, message.type);
|
||||||
|
JsonArray messageResult = (JsonArray) message.arguments[0];
|
||||||
|
assertEquals(42, messageResult.getAsInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ParseTwoMessages() {
|
||||||
|
String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E";
|
||||||
|
InvocationMessage[] messages = jsonHubProtocol.parseMessages(twoMessages);
|
||||||
|
assertEquals(2, messages.length);
|
||||||
|
|
||||||
|
// Check the first message
|
||||||
|
InvocationMessage message = messages[0];
|
||||||
|
assertEquals("one", message.target);
|
||||||
|
assertEquals(null, message.invocationId);
|
||||||
|
assertEquals(1, message.type);
|
||||||
|
JsonArray messageResult = (JsonArray) message.arguments[0];
|
||||||
|
assertEquals(42, messageResult.getAsInt());
|
||||||
|
|
||||||
|
// Check the second message
|
||||||
|
InvocationMessage secondMessage = messages[1];
|
||||||
|
assertEquals("two", secondMessage.target);
|
||||||
|
assertEquals(null, secondMessage.invocationId);
|
||||||
|
assertEquals(1, secondMessage.type);
|
||||||
|
JsonArray secondMessageResult = (JsonArray) secondMessage.arguments[0];
|
||||||
|
assertEquals(43, secondMessageResult.getAsInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ParseSingleMessageMutipleArgs() {
|
||||||
|
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E";
|
||||||
|
InvocationMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
|
||||||
|
|
||||||
|
//We know it's only one message
|
||||||
|
InvocationMessage message = messages[0];
|
||||||
|
assertEquals("test", message.target);
|
||||||
|
assertEquals(null, message.invocationId);
|
||||||
|
assertEquals(1, message.type);
|
||||||
|
JsonArray messageResult = ((JsonArray) message.arguments[0]);
|
||||||
|
assertEquals(42, messageResult.get(0).getAsInt());
|
||||||
|
assertEquals(24, messageResult.get(1).getAsInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue