[Java] Log invocation binding failures (#3160)

This commit is contained in:
BrennanConroy 2018-10-30 13:49:43 -07:00 committed by GitHub
parent 73f2f19984
commit c9600ac2d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 124 additions and 26 deletions

View File

@ -167,6 +167,10 @@ public class HubConnection {
for (HubMessage message : messages) { for (HubMessage message : messages) {
logger.debug("Received message of type {}.", message.getMessageType()); logger.debug("Received message of type {}.", message.getMessageType());
switch (message.getMessageType()) { switch (message.getMessageType()) {
case INVOCATION_BINDING_FAILURE:
InvocationBindingFailureMessage msg = (InvocationBindingFailureMessage)message;
logger.error("Failed to bind arguments received in invocation '{}' of '{}'.", msg.getInvocationId(), msg.getTarget(), msg.getException());
break;
case INVOCATION: case INVOCATION:
InvocationMessage invocationMessage = (InvocationMessage) message; InvocationMessage invocationMessage = (InvocationMessage) message;
List<InvocationHandler> handlers = this.handlers.get(invocationMessage.getTarget()); List<InvocationHandler> handlers = this.handlers.get(invocationMessage.getTarget());

View File

@ -10,7 +10,8 @@ enum HubMessageType {
STREAM_INVOCATION(4), STREAM_INVOCATION(4),
CANCEL_INVOCATION(5), CANCEL_INVOCATION(5),
PING(6), PING(6),
CLOSE(7); CLOSE(7),
INVOCATION_BINDING_FAILURE(-1);
public int value; public int value;
HubMessageType(int id) { this.value = id; } HubMessageType(int id) { this.value = id; }

View File

@ -0,0 +1,33 @@
// 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.signalr;
class InvocationBindingFailureMessage extends HubMessage {
private final String invocationId;
private final String target;
private final Exception exception;
public InvocationBindingFailureMessage(String invocationId, String target, Exception exception) {
this.invocationId = invocationId;
this.target = target;
this.exception = exception;
}
public String getInvocationId() {
return invocationId;
}
public String getTarget() {
return target;
}
public Exception getException() {
return exception;
}
@Override
public HubMessageType getMessageType() {
return HubMessageType.INVOCATION_BINDING_FAILURE;
}
}

View File

@ -52,6 +52,7 @@ class JsonHubProtocol implements HubProtocol {
ArrayList<Object> arguments = null; ArrayList<Object> arguments = null;
JsonArray argumentsToken = null; JsonArray argumentsToken = null;
Object result = null; Object result = null;
Exception argumentBindingException = null;
JsonElement resultToken = null; JsonElement resultToken = null;
JsonReader reader = new JsonReader(new StringReader(str)); JsonReader reader = new JsonReader(new StringReader(str));
reader.beginObject(); reader.beginObject();
@ -83,8 +84,26 @@ class JsonHubProtocol implements HubProtocol {
break; break;
case "arguments": case "arguments":
if (target != null) { if (target != null) {
List<Class<?>> types = binder.getParameterTypes(target); boolean startedArray = false;
arguments = bindArguments(reader, types); try {
List<Class<?>> types = binder.getParameterTypes(target);
startedArray = true;
arguments = bindArguments(reader, types);
} catch (Exception ex) {
argumentBindingException = ex;
// Could be at any point in argument array JSON when an error is thrown
// Read until the end of the argument JSON array
if (!startedArray) {
reader.beginArray();
}
while (reader.hasNext()) {
reader.skipValue();
}
if (reader.peek() == JsonToken.END_ARRAY) {
reader.endArray();
}
}
} else { } else {
argumentsToken = (JsonArray)jsonParser.parse(reader); argumentsToken = (JsonArray)jsonParser.parse(reader);
} }
@ -104,13 +123,21 @@ class JsonHubProtocol implements HubProtocol {
switch (messageType) { switch (messageType) {
case INVOCATION: case INVOCATION:
if (argumentsToken != null) { if (argumentsToken != null) {
List<Class<?>> types = binder.getParameterTypes(target); try {
arguments = bindArguments(argumentsToken, types); List<Class<?>> types = binder.getParameterTypes(target);
arguments = bindArguments(argumentsToken, types);
} catch (Exception ex) {
argumentBindingException = ex;
}
} }
if (arguments == null) { if (argumentBindingException != null) {
hubMessages.add(new InvocationMessage(invocationId, target, new Object[0])); hubMessages.add(new InvocationBindingFailureMessage(invocationId, target, argumentBindingException));
} else { } else {
hubMessages.add(new InvocationMessage(invocationId, target, arguments.toArray())); if (arguments == null) {
hubMessages.add(new InvocationMessage(invocationId, target, new Object[0]));
} else {
hubMessages.add(new InvocationMessage(invocationId, target, arguments.toArray()));
}
} }
break; break;
case COMPLETION: case COMPLETION:
@ -183,7 +210,11 @@ class JsonHubProtocol implements HubProtocol {
throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argCount, paramCount)); throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argCount, paramCount));
} }
// Do this at the very end, because if we throw for any reason above, we catch at the call site
// And manually consume the rest of the array, if we called endArray before throwing the RuntimeException
// Then we can't correctly consume the rest of the json object
reader.endArray(); reader.endArray();
return arguments; return arguments;
} }
} }

View File

@ -81,12 +81,7 @@ class OkHttpWebSocketWrapper extends WebSocketWrapper {
@Override @Override
public void onMessage(WebSocket webSocket, String message) { public void onMessage(WebSocket webSocket, String message) {
try { onReceive.invoke(message);
onReceive.invoke(message);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
@Override @Override

View File

@ -904,7 +904,7 @@ class HubConnectionTest {
} }
@Test @Test
public void errorWhenReceivingInvokeWithIncorrectArgumentLength() { public void doesNotErrorWhenReceivingInvokeWithIncorrectArgumentLength() {
MockTransport mockTransport = new MockTransport(); MockTransport mockTransport = new MockTransport();
HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport);
hubConnection.on("Send", (s) -> { hubConnection.on("Send", (s) -> {
@ -913,8 +913,8 @@ class HubConnectionTest {
hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait();
RuntimeException exception = assertThrows(RuntimeException.class, () -> mockTransport.receiveMessage("{\"type\":1,\"target\":\"Send\",\"arguments\":[]}" + RECORD_SEPARATOR)); mockTransport.receiveMessage("{\"type\":1,\"target\":\"Send\",\"arguments\":[]}" + RECORD_SEPARATOR);
assertEquals("Invocation provides 0 argument(s) but target expects 1.", exception.getMessage()); hubConnection.stop();
} }
@Test @Test

View File

@ -220,30 +220,64 @@ class JsonHubProtocolTest {
} }
@Test @Test
public void errorWhileParsingTooManyArgumentsWithOutOfOrderProperties() { public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() {
String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 }));
RuntimeException exception = assertThrows(RuntimeException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
assertEquals("Invocation provides 2 argument(s) but target expects 1.", exception.getMessage()); assertEquals(1, messages.length);
assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass());
InvocationBindingFailureMessage message = (InvocationBindingFailureMessage)messages[0];
assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage());
} }
@Test @Test
public void errorWhileParsingTooManyArguments() { public void invocationBindingFailureWhileParsingTooManyArguments() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 }));
RuntimeException exception = assertThrows(RuntimeException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
assertEquals("Invocation provides 2 argument(s) but target expects 1.", exception.getMessage()); assertEquals(1, messages.length);
assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass());
InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0];
assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage());
} }
@Test @Test
public void errorWhileParsingTooFewArguments() { public void invocationBindingFailureWhileParsingTooFewArguments() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42, 24 })); TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42, 24 }));
RuntimeException exception = assertThrows(RuntimeException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
assertEquals("Invocation provides 1 argument(s) but target expects 2.", exception.getMessage()); assertEquals(1, messages.length);
assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass());
InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0];
assertEquals("Invocation provides 1 argument(s) but target expects 2.", message.getException().getMessage());
}
@Test
public void invocationBindingFailureWhenParsingIncorrectType() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
assertEquals(1, messages.length);
assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass());
InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0];
assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage());
}
@Test
public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E";
TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 }));
HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder);
assertEquals(1, messages.length);
assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass());
InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0];
assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage());
assertEquals("123", message.getInvocationId());
} }
@Test @Test