Merge pull request #2950 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
BrennanConroy 2018-09-14 11:37:05 -07:00 committed by GitHub
commit 7af3472ef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 475 additions and 136 deletions

View File

@ -7,23 +7,27 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
class CallbackMap {
private ConcurrentHashMap<String, List<ActionBase>> handlers = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, List<InvocationHandler>> handlers = new ConcurrentHashMap<>();
public InvocationHandler put(String target, ActionBase action, ArrayList<Class<?>> classes) {
InvocationHandler handler = new InvocationHandler(action, Collections.unmodifiableList(classes));
public void put(String target, ActionBase action) {
handlers.computeIfPresent(target, (methodName, handlerList) -> {
handlerList.add(action);
handlerList.add(handler);
return handlerList;
});
handlers.computeIfAbsent(target, (ac) -> new ArrayList<>(Arrays.asList(action)));
handlers.computeIfAbsent(target, (ac) -> new ArrayList<>(Arrays.asList(handler)));
return handler;
}
public Boolean containsKey(String key) {
return handlers.containsKey(key);
}
public List<ActionBase> get(String key) {
public List<InvocationHandler> get(String key) {
return handlers.get(key);
}

View File

@ -9,26 +9,24 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
public class HubConnection {
private String url;
private Transport transport;
private OnReceiveCallBack callback;
private CallbackMap handlers = new CallbackMap();
private HubProtocol protocol;
private Gson gson = new Gson();
private Boolean handshakeReceived = false;
private static final String RECORD_SEPARATOR = "\u001e";
private HubConnectionState connectionState = HubConnectionState.DISCONNECTED;
private HubConnectionState hubConnectionState = HubConnectionState.DISCONNECTED;
private Logger logger;
private List<Consumer<Exception>> onClosedCallbackList;
private boolean skipNegotiate = false;
private NegotiateResponse negotiateResponse;
private String accessToken;
private Map<String, String> headers = new HashMap<>();
private ConnectionState connectionState = null;
private static ArrayList<Class<?>> emptyArray = new ArrayList<>();
private static int MAX_NEGOTIATE_ATTEMPTS = 100;
public HubConnection(String url, Transport transport, Logger logger, boolean skipNegotiate) {
@ -61,24 +59,20 @@ public class HubConnection {
}
}
HubMessage[] messages = protocol.parseMessages(payload);
HubMessage[] messages = protocol.parseMessages(payload, connectionState);
for (HubMessage message : messages) {
logger.log(LogLevel.Debug, "Received message of type %s", message.getMessageType());
logger.log(LogLevel.Debug, "Received message of type %s.", message.getMessageType());
switch (message.getMessageType()) {
case INVOCATION:
InvocationMessage invocationMessage = (InvocationMessage) message;
if (handlers.containsKey(invocationMessage.target)) {
ArrayList<Object> args = gson.fromJson((JsonArray) invocationMessage.arguments[0], (new ArrayList<>()).getClass());
List<ActionBase> actions = handlers.get(invocationMessage.target);
if (actions != null) {
logger.log(LogLevel.Debug, "Invoking handlers for target %s", invocationMessage.target);
for (ActionBase action : actions) {
action.invoke(args.toArray());
}
List<InvocationHandler> handlers = this.handlers.get(invocationMessage.target);
if (handlers != null) {
for (InvocationHandler handler : handlers) {
handler.getAction().invoke(invocationMessage.arguments);
}
} else {
logger.log(LogLevel.Warning, "Failed to find handler for %s method", invocationMessage.target);
logger.log(LogLevel.Warning, "Failed to find handler for %s method.", invocationMessage.target);
}
break;
case CLOSE:
@ -93,7 +87,7 @@ public class HubConnection {
case STREAM_ITEM:
case CANCEL_INVOCATION:
case COMPLETION:
logger.log(LogLevel.Error, "This client does not support %s messages", message.getMessageType());
logger.log(LogLevel.Error, "This client does not support %s messages.", message.getMessageType());
throw new UnsupportedOperationException(String.format("The message type %s is not supported yet.", message.getMessageType()));
}
@ -148,7 +142,7 @@ public class HubConnection {
* @return HubConnection state enum.
*/
public HubConnectionState getConnectionState() {
return connectionState;
return hubConnectionState;
}
/**
@ -157,7 +151,7 @@ public class HubConnection {
* @throws Exception An error occurred while connecting.
*/
public void start() throws Exception {
if (connectionState != HubConnectionState.DISCONNECTED) {
if (hubConnectionState != HubConnectionState.DISCONNECTED) {
return;
}
if (!skipNegotiate) {
@ -198,7 +192,8 @@ public class HubConnection {
transport.start();
String handshake = HandshakeProtocol.createHandshakeRequestMessage(new HandshakeRequestMessage(protocol.getName(), protocol.getVersion()));
transport.send(handshake);
connectionState = HubConnectionState.CONNECTED;
hubConnectionState = HubConnectionState.CONNECTED;
connectionState = new ConnectionState(this);
logger.log(LogLevel.Information, "HubConnected started.");
}
@ -206,7 +201,7 @@ public class HubConnection {
* Stops a connection to the server.
*/
private void stop(String errorMessage) {
if (connectionState == HubConnectionState.DISCONNECTED) {
if (hubConnectionState == HubConnectionState.DISCONNECTED) {
return;
}
@ -217,7 +212,8 @@ public class HubConnection {
}
transport.stop();
connectionState = HubConnectionState.DISCONNECTED;
hubConnectionState = HubConnectionState.DISCONNECTED;
connectionState = null;
logger.log(LogLevel.Information, "HubConnection stopped.");
if (onClosedCallbackList != null) {
HubException hubException = new HubException(errorMessage);
@ -243,7 +239,7 @@ public class HubConnection {
* @throws Exception If there was an error while sending.
*/
public void send(String method, Object... args) throws Exception {
if (connectionState != HubConnectionState.CONNECTED) {
if (hubConnectionState != HubConnectionState.CONNECTED) {
throw new HubException("The 'send' method cannot be called if the connection is not active");
}
@ -262,9 +258,9 @@ public class HubConnection {
*/
public Subscription on(String target, Action callback) {
ActionBase action = args -> callback.invoke();
handlers.put(target, action);
InvocationHandler handler = handlers.put(target, action, emptyArray);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -278,9 +274,11 @@ public class HubConnection {
*/
public <T1> Subscription on(String target, Action1<T1> callback, Class<T1> param1) {
ActionBase action = params -> callback.invoke(param1.cast(params[0]));
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(1);
classes.add(param1);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -298,9 +296,12 @@ public class HubConnection {
ActionBase action = params -> {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(2);
classes.add(param1);
classes.add(param2);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -321,9 +322,13 @@ public class HubConnection {
ActionBase action = params -> {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(3);
classes.add(param1);
classes.add(param2);
classes.add(param3);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -346,9 +351,14 @@ public class HubConnection {
ActionBase action = params -> {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(4);
classes.add(param1);
classes.add(param2);
classes.add(param3);
classes.add(param4);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -374,9 +384,15 @@ public class HubConnection {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]),
param5.cast(params[4]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(5);
classes.add(param1);
classes.add(param2);
classes.add(param3);
classes.add(param4);
classes.add(param5);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -404,9 +420,16 @@ public class HubConnection {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]),
param5.cast(params[4]), param6.cast(params[5]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(6);
classes.add(param1);
classes.add(param2);
classes.add(param3);
classes.add(param4);
classes.add(param5);
classes.add(param6);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -436,9 +459,17 @@ public class HubConnection {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]),
param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(7);
classes.add(param1);
classes.add(param2);
classes.add(param3);
classes.add(param4);
classes.add(param5);
classes.add(param6);
classes.add(param7);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -470,9 +501,18 @@ public class HubConnection {
callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]),
param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]), param8.cast(params[7]));
};
handlers.put(target, action);
ArrayList<Class<?>> classes = new ArrayList<>(8);
classes.add(param1);
classes.add(param2);
classes.add(param3);
classes.add(param4);
classes.add(param5);
classes.add(param6);
classes.add(param7);
classes.add(param8);
InvocationHandler handler = handlers.put(target, action, classes);
logger.log(LogLevel.Trace, "Registering handler for client method: %s", target);
return new Subscription(handlers, action, target);
return new Subscription(handlers, handler, target);
}
/**
@ -492,4 +532,32 @@ public class HubConnection {
onClosedCallbackList.add(callback);
}
private class ConnectionState implements InvocationBinder {
HubConnection connection;
public ConnectionState(HubConnection connection) {
this.connection = connection;
}
@Override
public Class<?> getReturnType(String invocationId) {
return null;
}
@Override
public List<Class<?>> getParameterTypes(String methodName) throws Exception {
List<InvocationHandler> handlers = connection.handlers.get(methodName);
if (handlers == null) {
logger.log(LogLevel.Warning, "Failed to find handler for '%s' method.", methodName);
return emptyArray;
}
if (handlers.size() == 0) {
throw new Exception(String.format("There are no callbacks registered for the method '%s'.", methodName));
}
return handlers.get(0).getClasses();
}
}
}

View File

@ -3,6 +3,8 @@
package com.microsoft.aspnet.signalr;
import java.io.IOException;
/**
* A protocol abstraction for communicating with SignalR hubs.
*/
@ -16,7 +18,7 @@ interface HubProtocol {
* @param message A string representation of one or more {@link HubMessage}s.
* @return A list of {@link HubMessage}s.
*/
HubMessage[] parseMessages(String message);
HubMessage[] parseMessages(String message, InvocationBinder binder) throws Exception;
/**
* Writes the specified {@link HubMessage} to a String.

View File

@ -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.
package com.microsoft.aspnet.signalr;
import java.util.List;
interface InvocationBinder {
Class<?> getReturnType(String invocationId);
List<Class<?>> getParameterTypes(String methodName) throws Exception;
}

View File

@ -0,0 +1,24 @@
// 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.
package com.microsoft.aspnet.signalr;
import java.util.List;
class InvocationHandler {
private List<Class<?>> classes;
private ActionBase action;
InvocationHandler(ActionBase action, List<Class<?>> classes) {
this.action = action;
this.classes = classes;
}
public List<Class<?>> getClasses() {
return classes;
}
public ActionBase getAction() {
return action;
}
}

View File

@ -11,7 +11,7 @@ class InvocationMessage extends HubMessage {
public InvocationMessage(String target, Object[] args) {
this.target = target;
arguments = args;
this.arguments = args;
}
public String getInvocationId() {

View File

@ -3,13 +3,16 @@
package com.microsoft.aspnet.signalr;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
class JsonHubProtocol implements HubProtocol {
private final JsonParser jsonParser = new JsonParser();
@ -32,41 +35,105 @@ class JsonHubProtocol implements HubProtocol {
}
@Override
public HubMessage[] parseMessages(String payload) {
public HubMessage[] parseMessages(String payload, InvocationBinder binder) throws Exception {
String[] messages = payload.split(RECORD_SEPARATOR);
List<HubMessage> hubMessages = new ArrayList<>();
for (String splitMessage : messages) {
for (String str : messages) {
HubMessageType messageType = null;
String invocationId = null;
String target = null;
String error = null;
ArrayList<Object> arguments = null;
JsonArray argumentsToken = null;
JsonReader reader = new JsonReader(new StringReader(str));
reader.beginObject();
do {
String name = reader.nextName();
switch (name) {
case "type":
messageType = HubMessageType.values()[reader.nextInt() - 1];
break;
case "invocationId":
invocationId = reader.nextString();
break;
case "target":
target = reader.nextString();
break;
case "error":
error = reader.nextString();
break;
case "result":
reader.skipValue();
break;
case "item":
reader.skipValue();
break;
case "arguments":
if (target != null) {
reader.beginArray();
List<Class<?>> types = binder.getParameterTypes(target);
if (types != null && types.size() >= 1) {
arguments = new ArrayList<>();
for (int i = 0; i < types.size(); i++) {
arguments.add(gson.fromJson(reader, types.get(i)));
}
}
reader.endArray();
} else {
argumentsToken = (JsonArray)jsonParser.parse(reader);
}
break;
case "headers":
throw new HubException("Headers not implemented yet.");
default:
// Skip unknown property, allows new clients to still work with old protocols
reader.skipValue();
break;
}
} while (reader.hasNext());
reader.endObject();
reader.close();
JsonObject jsonMessage = jsonParser.parse(splitMessage).getAsJsonObject();
HubMessageType messageType = HubMessageType.values()[jsonMessage.get("type").getAsInt() -1];
switch (messageType) {
case INVOCATION:
//Invocation Message
String target = jsonMessage.get("target").getAsString();
JsonElement args = jsonMessage.get("arguments");
hubMessages.add(new InvocationMessage(target, new Object[] {args}));
if (argumentsToken != null) {
List<Class<?>> types = binder.getParameterTypes(target);
if (types != null && types.size() >= 1) {
arguments = new ArrayList<>();
for (int i = 0; i < types.size(); i++) {
arguments.add(gson.fromJson(argumentsToken.get(i), types.get(i)));
}
}
}
if (arguments == null) {
hubMessages.add(new InvocationMessage(target, new Object[0]));
} else {
hubMessages.add(new InvocationMessage(target, arguments.toArray()));
}
break;
case STREAM_INVOCATION:
case STREAM_ITEM:
case COMPLETION:
case STREAM_INVOCATION:
case CANCEL_INVOCATION:
throw new UnsupportedOperationException(String.format("The message type %s is not supported yet.", messageType));
case PING:
//Ping
hubMessages.add(new PingMessage());
break;
case CLOSE:
CloseMessage closeMessage;
if (jsonMessage.has("error")) {
String error = jsonMessage.get("error").getAsString();
closeMessage = new CloseMessage(error);
if (error != null) {
hubMessages.add(new CloseMessage(error));
} else {
closeMessage = new CloseMessage();
hubMessages.add(new CloseMessage());
}
hubMessages.add(closeMessage);
break;
default:
break;
}
}
return hubMessages.toArray(new HubMessage[hubMessages.size()]);
}

View File

@ -0,0 +1,19 @@
// 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.
package com.microsoft.aspnet.signalr;
class StreamInvocationMessage extends InvocationMessage {
int type = HubMessageType.STREAM_INVOCATION.value;
public StreamInvocationMessage(String invocationId, String target, Object[] arguments) {
super(target, arguments);
this.invocationId = invocationId;
}
@Override
public HubMessageType getMessageType() {
return HubMessageType.STREAM_INVOCATION;
}
}

View File

@ -7,19 +7,19 @@ import java.util.List;
public class Subscription {
private CallbackMap handlers;
private ActionBase action;
private InvocationHandler handler;
private String target;
public Subscription(CallbackMap handlers, ActionBase action, String target) {
public Subscription(CallbackMap handlers, InvocationHandler handler, String target) {
this.handlers = handlers;
this.action = action;
this.handler = handler;
this.target = target;
}
public void unsubscribe() {
List<ActionBase> actions = this.handlers.get(target);
if (actions != null) {
actions.remove(action);
List<InvocationHandler> handler = this.handlers.get(target);
if (handler != null) {
handler.remove(this.handler);
}
}
}

View File

@ -31,7 +31,7 @@ public class HubConnectionTest {
}
@Test
public void HubConnectionClosesAfterCloseMessage() throws Exception {
public void hubConnectionClosesAfterCloseMessage() throws Exception {
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -46,7 +46,7 @@ public class HubConnectionTest {
}
@Test
public void HubConnectionReceiveHandshakeResponseWithError() throws Exception {
public void hubConnectionReceiveHandshakeResponseWithError() throws Exception {
exceptionRule.expect(HubException.class);
exceptionRule.expectMessage("Requested protocol 'messagepack' is not available.");
@ -58,7 +58,7 @@ public class HubConnectionTest {
}
@Test
public void RegisteringMultipleHandlersAndBothGetTriggered() throws Exception {
public void registeringMultipleHandlersAndBothGetTriggered() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -84,7 +84,7 @@ public class HubConnectionTest {
}
@Test
public void RemoveHandlerByName() throws Exception {
public void removeHandlerByName() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -111,7 +111,7 @@ public class HubConnectionTest {
}
@Test
public void AddAndRemoveHandlerImmediately() throws Exception {
public void addAndRemoveHandlerImmediately() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -132,11 +132,11 @@ public class HubConnectionTest {
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
// Confirming that the handler was removed.
assertEquals(0, value.get(), 0);
assertEquals(0.0, value.get(), 0);
}
@Test
public void RemovingMultipleHandlersWithOneCallToRemove() throws Exception {
public void removingMultipleHandlersWithOneCallToRemove() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -168,7 +168,7 @@ public class HubConnectionTest {
}
@Test
public void RemoveHandlerWithUnsubscribe() throws Exception {
public void removeHandlerWithUnsubscribe() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -191,12 +191,17 @@ public class HubConnectionTest {
assertEquals(1, value.get(), 0);
subscription.unsubscribe();
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
try {
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
} catch (Exception ex) {
assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage());
}
assertEquals(1, value.get(), 0);
}
@Test
public void UnsubscribeTwice() throws Exception {
public void unsubscribeTwice() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -220,12 +225,17 @@ public class HubConnectionTest {
subscription.unsubscribe();
subscription.unsubscribe();
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
try {
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
} catch (Exception ex) {
assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage());
}
assertEquals(1, value.get(), 0);
}
@Test
public void RemoveSingleHandlerWithUnsubscribe() throws Exception {
public void removeSingleHandlerWithUnsubscribe() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -255,7 +265,7 @@ public class HubConnectionTest {
}
@Test
public void AddAndRemoveHandlerImmediatelyWithSubscribe() throws Exception {
public void addAndRemoveHandlerImmediatelyWithSubscribe() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -268,13 +278,19 @@ public class HubConnectionTest {
hubConnection.start();
mockTransport.receiveMessage("{}" + RECORD_SEPARATOR);
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
try {
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR);
} catch (Exception ex) {
assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage());
}
// Confirming that the handler was removed.
assertEquals(0, value.get(), 0);
}
@Test
public void RegisteringMultipleHandlersThatTakeParamsAndBothGetTriggered() throws Exception {
public void registeringMultipleHandlersThatTakeParamsAndBothGetTriggered() throws Exception {
AtomicReference<Double> value = new AtomicReference<>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -296,7 +312,7 @@ public class HubConnectionTest {
// We're using AtomicReference<Double> in the send tests instead of int here because Gson has trouble deserializing to Integer
@Test
public void SendWithNoParamsTriggersOnHandler() throws Exception {
public void sendWithNoParamsTriggersOnHandler() throws Exception {
AtomicReference<Double> value = new AtomicReference<Double>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -315,7 +331,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithParamTriggersOnHandler() throws Exception {
public void sendWithParamTriggersOnHandler() throws Exception {
AtomicReference<String> value = new AtomicReference<>();
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -335,7 +351,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithTwoParamsTriggersOnHandler() throws Exception {
public void sendWithTwoParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<Double> value2 = new AtomicReference<>();
@ -361,7 +377,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithThreeParamsTriggersOnHandler() throws Exception {
public void sendWithThreeParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -391,7 +407,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithFourParamsTriggersOnHandler() throws Exception {
public void sendWithFourParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -424,7 +440,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithFiveParamsTriggersOnHandler() throws Exception {
public void sendWithFiveParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -461,7 +477,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithSixParamsTriggersOnHandler() throws Exception {
public void sendWithSixParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -502,7 +518,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithSevenParamsTriggersOnHandler() throws Exception {
public void sendWithSevenParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -547,7 +563,7 @@ public class HubConnectionTest {
}
@Test
public void SendWithEightParamsTriggersOnHandler() throws Exception {
public void sendWithEightParamsTriggersOnHandler() throws Exception {
AtomicReference<String> value1 = new AtomicReference<>();
AtomicReference<String> value2 = new AtomicReference<>();
AtomicReference<String> value3 = new AtomicReference<>();
@ -594,8 +610,40 @@ public class HubConnectionTest {
assertEquals("F", value8.get());
}
private class Custom {
public int number;
public String str;
public boolean[] bools;
}
@Test
public void ReceiveHandshakeResponseAndMessage() throws Exception {
public void sendWithCustomObjectTriggersOnHandler() throws Exception {
AtomicReference<Custom> value1 = new AtomicReference<>();
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
hubConnection.on("inc", (param1) -> {
assertNull(value1.get());
value1.set(param1);
}, Custom.class);
hubConnection.start();
mockTransport.receiveMessage("{}" + RECORD_SEPARATOR);
mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[{\"number\":1,\"str\":\"A\",\"bools\":[true,false]}]}" + RECORD_SEPARATOR);
// Confirming that our handler was called and the correct message was passed in.
Custom custom = value1.get();
assertEquals(1, custom.number);
assertEquals("A", custom.str);
assertEquals(2, custom.bools.length);
assertEquals(true, custom.bools[0]);
assertEquals(false, custom.bools[1]);
}
@Test
public void receiveHandshakeResponseAndMessage() throws Exception {
AtomicReference<Double> value = new AtomicReference<Double>(0.0);
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
@ -660,7 +708,7 @@ public class HubConnectionTest {
}
@Test
public void HubConnectionClosesAndRunsOnClosedCallbackAfterCloseMessageWithError() throws Exception {
public void hubConnectionClosesAndRunsOnClosedCallbackAfterCloseMessageWithError() throws Exception {
MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
hubConnection.onClosed((ex) -> {
@ -677,7 +725,7 @@ public class HubConnectionTest {
}
@Test
public void CallingStartOnStartedHubConnectionNoOps() throws Exception {
public void callingStartOnStartedHubConnectionNoOps() throws Exception {
Transport mockTransport = new MockTransport();
HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true);
hubConnection.start();
@ -691,7 +739,7 @@ public class HubConnectionTest {
}
@Test
public void CannotSendBeforeStart() throws Exception {
public void cannotSendBeforeStart() throws Exception {
exceptionRule.expect(HubException.class);
exceptionRule.expectMessage("The 'send' method cannot be called if the connection is not active");

View File

@ -5,6 +5,11 @@ package com.microsoft.aspnet.signalr;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@ -30,7 +35,7 @@ public class JsonHubProtocolTest {
}
@Test
public void VerifyWriteMessage() {
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";
@ -38,9 +43,11 @@ public class JsonHubProtocolTest {
}
@Test
public void ParsePingMessage() {
public void parsePingMessage() throws Exception {
String stringifiedMessage = "{\"type\":6}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(new PingMessage());
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
//We know it's only one message
assertEquals(1, messages.length);
@ -48,9 +55,11 @@ public class JsonHubProtocolTest {
}
@Test
public void ParseCloseMessage() {
public void parseCloseMessage() throws Exception {
String stringifiedMessage = "{\"type\":7}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(new CloseMessage());
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
//We know it's only one message
assertEquals(1, messages.length);
@ -64,9 +73,11 @@ public class JsonHubProtocolTest {
}
@Test
public void ParseCloseMessageWithError() {
public void parseCloseMessageWithError() throws Exception {
String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(new CloseMessage("There was an error"));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
//We know it's only one message
assertEquals(1, messages.length);
@ -80,9 +91,11 @@ public class JsonHubProtocolTest {
}
@Test
public void ParseSingleMessage() {
public void parseSingleMessage() throws Exception {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
//We know it's only one message
assertEquals(1, messages.length);
@ -95,50 +108,59 @@ public class JsonHubProtocolTest {
assertEquals("test", invocationMessage.getTarget());
assertEquals(null, invocationMessage.getInvocationId());
JsonArray messageResult = (JsonArray) invocationMessage.getArguments()[0];
assertEquals(42, messageResult.getAsInt());
int messageResult = (int)invocationMessage.getArguments()[0];
assertEquals(42, messageResult);
}
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
@Test
public void ParseSingleUnsupportedStreamItemMessage() {
public void parseSingleUnsupportedStreamItemMessage() throws Exception {
exceptionRule.expect(UnsupportedOperationException.class);
exceptionRule.expectMessage("The message type STREAM_ITEM is not supported yet.");
String stringifiedMessage = "{\"type\":2,\"Id\":1,\"Item\":42}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(null);
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
}
@Test
public void ParseSingleUnsupportedStreamInvocationMessage() {
public void parseSingleUnsupportedStreamInvocationMessage() throws Exception {
exceptionRule.expect(UnsupportedOperationException.class);
exceptionRule.expectMessage("The message type STREAM_INVOCATION is not supported yet.");
String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
TestBinder binder = new TestBinder(new StreamInvocationMessage("1", "test", new Object[] { 42 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
}
@Test
public void ParseSingleUnsupportedCancelInvocationMessage() {
public void parseSingleUnsupportedCancelInvocationMessage() throws Exception {
exceptionRule.expect(UnsupportedOperationException.class);
exceptionRule.expectMessage("The message type CANCEL_INVOCATION is not supported yet.");
String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(null);
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
}
@Test
public void ParseSingleUnsupportedCompletionMessage() {
public void parseSingleUnsupportedCompletionMessage() throws Exception {
exceptionRule.expect(UnsupportedOperationException.class);
exceptionRule.expectMessage("The message type COMPLETION is not supported yet.");
String stringifiedMessage = "{\"type\":3,\"invocationId\":123}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(null);
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
}
@Test
public void ParseTwoMessages() {
public void parseTwoMessages() throws Exception {
String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages);
TestBinder binder = new TestBinder(new InvocationMessage("one", new Object[] { 42 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages, binder);
assertEquals(2, messages.length);
// Check the first message
@ -149,8 +171,8 @@ public class JsonHubProtocolTest {
assertEquals("one", invocationMessage.getTarget());
assertEquals(null, invocationMessage.getInvocationId());
JsonArray messageResult = (JsonArray) invocationMessage.getArguments()[0];
assertEquals(42, messageResult.getAsInt());
int messageResult = (int)invocationMessage.getArguments()[0];
assertEquals(42, messageResult);
// Check the second message
assertEquals(HubMessageType.INVOCATION, messages[1].getMessageType());
@ -160,14 +182,16 @@ public class JsonHubProtocolTest {
assertEquals("two", invocationMessage2.getTarget());
assertEquals(null, invocationMessage2.getInvocationId());
JsonArray secondMessageResult = (JsonArray) invocationMessage2.getArguments()[0];
assertEquals(43, secondMessageResult.getAsInt());
int secondMessageResult = (int)invocationMessage2.getArguments()[0];
assertEquals(43, secondMessageResult);
}
@Test
public void ParseSingleMessageMutipleArgs() {
public void parseSingleMessageMutipleArgs() throws Exception {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E";
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42, 24 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
//We know it's only one message
assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType());
@ -175,8 +199,72 @@ public class JsonHubProtocolTest {
InvocationMessage message = (InvocationMessage)messages[0];
assertEquals("test", message.getTarget());
assertEquals(null, message.getInvocationId());
JsonArray messageResult = ((JsonArray) message.getArguments()[0]);
assertEquals(42, messageResult.get(0).getAsInt());
assertEquals(24, messageResult.get(1).getAsInt());
int messageResult = (int) message.getArguments()[0];
int messageResult2 = (int) message.getArguments()[1];
assertEquals(42, messageResult);
assertEquals(24, messageResult2);
}
@Test
public void parseMessageWithOutOfOrderProperties() throws Exception {
String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42, 24 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
// We know it's only one message
assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType());
InvocationMessage message = (InvocationMessage) messages[0];
assertEquals("test", message.getTarget());
assertEquals(null, message.getInvocationId());
int messageResult = (int) message.getArguments()[0];
int messageResult2 = (int) message.getArguments()[1];
assertEquals(42, messageResult);
assertEquals(24, messageResult2);
}
private class TestBinder implements InvocationBinder {
private Class<?>[] paramTypes = null;
public TestBinder(HubMessage expectedMessage) {
if (expectedMessage == null) {
return;
}
switch (expectedMessage.getMessageType()) {
case STREAM_INVOCATION:
ArrayList<Class<?>> streamTypes = new ArrayList<>();
for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) {
streamTypes.add(obj.getClass());
}
paramTypes = streamTypes.toArray(new Class<?>[streamTypes.size()]);
break;
case INVOCATION:
ArrayList<Class<?>> types = new ArrayList<>();
for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) {
types.add(obj.getClass());
}
paramTypes = types.toArray(new Class<?>[types.size()]);
break;
case STREAM_ITEM:
break;
default:
break;
}
}
@Override
public Class<?> getReturnType(String invocationId) {
return null;
}
@Override
public List<Class<?>> getParameterTypes(String methodName) {
if (paramTypes == null) {
return new ArrayList<>();
}
return new ArrayList<Class<?>>(Arrays.asList(paramTypes));
}
}
}

View File

@ -228,17 +228,17 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
completed = true;
break;
default:
throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading handshake request JSON.");
throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading handshake request JSON. Message content: {GetPayloadAsString()}");
}
}
if (protocol == null)
{
throw new InvalidDataException($"Missing required property '{ProtocolPropertyName}'.");
throw new InvalidDataException($"Missing required property '{ProtocolPropertyName}'. Message content: {GetPayloadAsString()}");
}
if (protocolVersion == null)
{
throw new InvalidDataException($"Missing required property '{ProtocolVersionPropertyName}'.");
throw new InvalidDataException($"Missing required property '{ProtocolVersionPropertyName}'. Message content: {GetPayloadAsString()}");
}
requestMessage = new HandshakeRequestMessage(protocol, protocolVersion.Value);
@ -249,6 +249,13 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
Utf8BufferTextReader.Return(textReader);
}
// For error messages, we want to print the payload as text
string GetPayloadAsString()
{
// REVIEW: Should we show hex for binary charaters?
return Encoding.UTF8.GetString(payload.ToArray());
}
return true;
}
}

View File

@ -61,10 +61,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
[InlineData("42\u001e", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")]
[InlineData("\"42\"\u001e", "Unexpected JSON Token Type 'String'. Expected a JSON Object.")]
[InlineData("null\u001e", "Unexpected JSON Token Type 'Null'. Expected a JSON Object.")]
[InlineData("{}\u001e", "Missing required property 'protocol'.")]
[InlineData("{}\u001e", "Missing required property 'protocol'. Message content: {}")]
[InlineData("[]\u001e", "Unexpected JSON Token Type 'Array'. Expected a JSON Object.")]
[InlineData("{\"protocol\":\"json\"}\u001e", "Missing required property 'version'.")]
[InlineData("{\"version\":1}\u001e", "Missing required property 'protocol'.")]
[InlineData("{\"protocol\":\"json\"}\u001e", "Missing required property 'version'. Message content: {\"protocol\":\"json\"}")]
[InlineData("{\"version\":1}\u001e", "Missing required property 'protocol'. Message content: {\"version\":1}")]
[InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}\u001e", "Missing required property 'protocol'. Message content: {\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}")]
[InlineData("{\"version\":\"123\"}\u001e", "Expected 'version' to be of type Integer.")]
[InlineData("{\"protocol\":null,\"version\":123}\u001e", "Expected 'protocol' to be of type String.")]
public void ParsingHandshakeRequestMessageThrowsForInvalidMessages(string payload, string expectedMessage)