Issue #207 - Support javax.websocket version 1.1

This commit is contained in:
Joakim Erdfelt 2016-08-16 16:24:33 -07:00
parent 795246c785
commit fc9adbb391
14 changed files with 237 additions and 104 deletions

View File

@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -74,6 +75,7 @@ public class AvailableDecoders implements Predicate<Class<?>>
public AvailableDecoders(EndpointConfig config)
{
Objects.requireNonNull(config);
this.config = config;
registeredDecoders = new LinkedList<>();
@ -103,6 +105,9 @@ public class AvailableDecoders implements Predicate<Class<?>>
// STREAMING based
registerPrimitive(ReaderDecoder.class, Decoder.TextStream.class, Reader.class);
registerPrimitive(InputStreamDecoder.class, Decoder.BinaryStream.class, InputStreamDecoder.class);
// Config Based
registerAll(config.getDecoders());
}
private void registerPrimitive(Class<? extends Decoder> decoderClass, Class<? extends Decoder> interfaceType, Class<?> type)
@ -189,6 +194,12 @@ public class AvailableDecoders implements Predicate<Class<?>>
.findFirst()
.get();
if (conflicts.decoder.equals(decoder) && conflicts.implementsInterface(interfaceClass))
{
// Same decoder as what is there already, don't bother adding it again.
return;
}
StringBuilder err = new StringBuilder();
err.append("Duplicate Decoder Object type ");
err.append(objectType.getName());

View File

@ -41,13 +41,20 @@ public class AvailableEncoders implements Predicate<Class<?>>
public final Class<? extends Encoder> encoder;
public final Class<? extends Encoder> interfaceType;
public final Class<?> objectType;
public final boolean primitive;
public Encoder instance;
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType)
{
this(encoder, interfaceType, objectType, false);
}
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType, boolean primitive)
{
this.encoder = encoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
}
public boolean implementsInterface(Class<? extends Encoder> type)
@ -71,27 +78,27 @@ public class AvailableEncoders implements Predicate<Class<?>>
registeredEncoders = new LinkedList<>();
// TEXT based [via Class reference]
register(BooleanEncoder.class, Encoder.Text.class, Boolean.class);
register(ByteEncoder.class, Encoder.Text.class, Byte.class);
register(CharacterEncoder.class, Encoder.Text.class, Character.class);
register(DoubleEncoder.class, Encoder.Text.class, Double.class);
register(FloatEncoder.class, Encoder.Text.class, Float.class);
register(IntegerEncoder.class, Encoder.Text.class, Integer.class);
register(LongEncoder.class, Encoder.Text.class, Long.class);
register(StringEncoder.class, Encoder.Text.class, String.class);
registerPrimitive(BooleanEncoder.class, Encoder.Text.class, Boolean.class);
registerPrimitive(ByteEncoder.class, Encoder.Text.class, Byte.class);
registerPrimitive(CharacterEncoder.class, Encoder.Text.class, Character.class);
registerPrimitive(DoubleEncoder.class, Encoder.Text.class, Double.class);
registerPrimitive(FloatEncoder.class, Encoder.Text.class, Float.class);
registerPrimitive(IntegerEncoder.class, Encoder.Text.class, Integer.class);
registerPrimitive(LongEncoder.class, Encoder.Text.class, Long.class);
registerPrimitive(StringEncoder.class, Encoder.Text.class, String.class);
// TEXT based [via Primitive reference]
register(BooleanEncoder.class, Encoder.Text.class, Boolean.TYPE);
register(ByteEncoder.class, Encoder.Text.class, Byte.TYPE);
register(CharacterEncoder.class, Encoder.Text.class, Character.TYPE);
register(DoubleEncoder.class, Encoder.Text.class, Double.TYPE);
register(FloatEncoder.class, Encoder.Text.class, Float.TYPE);
register(IntegerEncoder.class, Encoder.Text.class, Integer.TYPE);
register(LongEncoder.class, Encoder.Text.class, Long.TYPE);
registerPrimitive(BooleanEncoder.class, Encoder.Text.class, Boolean.TYPE);
registerPrimitive(ByteEncoder.class, Encoder.Text.class, Byte.TYPE);
registerPrimitive(CharacterEncoder.class, Encoder.Text.class, Character.TYPE);
registerPrimitive(DoubleEncoder.class, Encoder.Text.class, Double.TYPE);
registerPrimitive(FloatEncoder.class, Encoder.Text.class, Float.TYPE);
registerPrimitive(IntegerEncoder.class, Encoder.Text.class, Integer.TYPE);
registerPrimitive(LongEncoder.class, Encoder.Text.class, Long.TYPE);
// BINARY based
register(ByteBufferEncoder.class, Encoder.Binary.class, ByteBuffer.class);
register(ByteArrayEncoder.class, Encoder.Binary.class, byte[].class);
registerPrimitive(ByteBufferEncoder.class, Encoder.Binary.class, ByteBuffer.class);
registerPrimitive(ByteArrayEncoder.class, Encoder.Binary.class, byte[].class);
// STREAMING based
// Note: Streams (Writer / OutputStream) are not present here
@ -99,11 +106,14 @@ public class AvailableEncoders implements Predicate<Class<?>>
// encoder to write an object to a Stream
// register(WriterEncoder.class, Encoder.TextStream.class, Writer.class);
// register(OutputStreamEncoder.class, Encoder.BinaryStream.class, OutputStream.class);
// Config Based
registerAll(config.getEncoders());
}
private void register(Class<? extends Encoder> encoderClass, Class<? extends Encoder> interfaceType, Class<?> type)
private void registerPrimitive(Class<? extends Encoder> encoderClass, Class<? extends Encoder> interfaceType, Class<?> type)
{
registeredEncoders.add(new RegisteredEncoder(encoderClass, interfaceType, type));
registeredEncoders.add(new RegisteredEncoder(encoderClass, interfaceType, type, true));
}
public void register(Class<? extends Encoder> encoder)
@ -177,7 +187,33 @@ public class AvailableEncoders implements Predicate<Class<?>>
throw new InvalidWebSocketException(err.toString());
}
registeredEncoders.add(new RegisteredEncoder(encoder, interfaceClass, objectType));
try
{
RegisteredEncoder conflicts = registeredEncoders.stream()
.filter(registered -> registered.isType(objectType))
.filter(registered -> !registered.primitive)
.findFirst()
.get();
if (conflicts.encoder.equals(encoder) && conflicts.implementsInterface(interfaceClass))
{
// Same encoder as what is there already, don't bother adding it again.
return;
}
StringBuilder err = new StringBuilder();
err.append("Duplicate Encoder Object type ");
err.append(objectType.getName());
err.append(" in ");
err.append(encoder.getName());
err.append(", previously declared in ");
err.append(conflicts.encoder.getName());
throw new InvalidWebSocketException(err.toString());
}
catch (NoSuchElementException e)
{
registeredEncoders.addFirst(new RegisteredEncoder(encoder, interfaceClass, objectType));
}
}
public List<RegisteredEncoder> supporting(Class<? extends Encoder> interfaceType)

View File

@ -115,6 +115,12 @@ public class JsrEndpointFunctions extends CommonEndpointFunctions<JsrSession>
{
this.delegateSink.accept(payload, fin);
}
@Override
public String toString()
{
return String.format("MessageSink[%s]",messageHandler.getClass().getName());
}
}
/**
@ -277,7 +283,7 @@ public class JsrEndpointFunctions extends CommonEndpointFunctions<JsrSession>
try
{
// Is this a PongMessage?
if (PongMessage.class.isAssignableFrom(PongMessage.class))
if (PongMessage.class.isAssignableFrom(clazz))
{
Function<ByteBuffer, Void> pongFunction = (payload) ->
{

View File

@ -254,10 +254,10 @@ public class EncoderTest
@Test
public void testSingleQuotes() throws Exception
{
EchoServer eserver = new EchoServer(server);
EchoServer echoServer = new EchoServer(server);
try
{
eserver.start();
echoServer.start();
QuotesSocket quoter = new QuotesSocket();
@ -278,17 +278,17 @@ public class EncoderTest
}
finally
{
eserver.stop();
echoServer.stop();
}
}
@Test
public void testTwoQuotes() throws Exception
{
EchoServer eserver = new EchoServer(server);
EchoServer echoServer = new EchoServer(server);
try
{
eserver.start();
echoServer.start();
QuotesSocket quoter = new QuotesSocket();
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
@ -312,7 +312,7 @@ public class EncoderTest
}
finally
{
eserver.stop();
echoServer.stop();
}
}
}

View File

@ -25,31 +25,53 @@ import javax.websocket.DeploymentException;
import javax.websocket.MessageHandler;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope;
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayWholeHandler;
import org.eclipse.jetty.websocket.jsr356.handlers.ByteBufferPartialHandler;
import org.eclipse.jetty.websocket.jsr356.handlers.LongMessageHandler;
import org.eclipse.jetty.websocket.jsr356.handlers.StringWholeHandler;
import org.eclipse.jetty.websocket.jsr356.samples.DummyEndpoint;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class JsrSessionTest
{
@Rule
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
private ClientContainer container;
private JsrSession session;
@Before
public void initSession()
public void initSession() throws Exception
{
String id = JsrSessionTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
// Container
container = new ClientContainer(new SimpleContainerScope(policy,bufferPool));
container.start();
LocalWebSocketConnection connection = new LocalWebSocketConnection(bufferPool);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(new DummyEndpoint(), config);
// Session
session = new JsrSession(container, id, requestURI, ei, connection);
session.start();
}
@After
public void stopSession() throws Exception
{
session.stop();
container.stop();
}
@Test
@ -68,10 +90,10 @@ public class JsrSessionTest
@Test
public void testMessageHandlerReplaceTextHandler() throws DeploymentException
{
MessageHandler oldText = new StringWholeHandler();
session.addMessageHandler(oldText); // add a TEXT handler
MessageHandler strHandler = new StringWholeHandler();
session.addMessageHandler(strHandler); // add a TEXT handler
session.addMessageHandler(new ByteArrayWholeHandler()); // add BINARY handler
session.removeMessageHandler(oldText); // remove original TEXT handler
session.removeMessageHandler(strHandler); // remove original TEXT handler
session.addMessageHandler(new LongMessageHandler()); // add new TEXT handler
}

View File

@ -35,6 +35,7 @@ import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.util.Hex;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.junit.BeforeClass;
@ -45,7 +46,7 @@ import org.junit.rules.ExpectedException;
public class AvailableEncodersTest
{
private static EndpointConfig testConfig;
@BeforeClass
public static void initConfig()
{
@ -56,7 +57,7 @@ public class AvailableEncodersTest
public ExpectedException expectedException = ExpectedException.none();
private AvailableEncoders encoders = new AvailableEncoders(testConfig);
public <T> void assertTextEncoder(Class<T> type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException
{
Encoder.Text<T> encoder = (Encoder.Text<T>) encoders.getInstanceFor(type);
@ -64,138 +65,138 @@ public class AvailableEncodersTest
String encoded = encoder.encode(value);
assertThat("Encoded", encoded, is(expectedEncoded));
}
public <T> void assertTextStreamEncoder(Class<T> type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
Encoder.TextStream<T> encoder = (Encoder.TextStream<T>) encoders.getInstanceFor(type);
assertThat("Encoder", encoder, notNullValue());
StringWriter writer = new StringWriter();
encoder.encode(value, writer);
assertThat("Encoded", writer.toString(), is(expectedEncoded));
}
public <T> void assertBinaryEncoder(Class<T> type, T value, String expectedEncodedHex) throws IllegalAccessException, InstantiationException, EncodeException
{
Encoder.Binary<T> encoder = (Encoder.Binary<T>) encoders.getInstanceFor(type);
assertThat("Encoder", encoder, notNullValue());
ByteBuffer encoded = encoder.encode(value);
String hexEncoded = Hex.asHex(encoded);
assertThat("Encoded", hexEncoded, is(expectedEncodedHex));
}
public <T> void assertBinaryStreamEncoder(Class<T> type, T value, String expectedEncodedHex) throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
Encoder.BinaryStream<T> encoder = (Encoder.BinaryStream<T>) encoders.getInstanceFor(type);
assertThat("Encoder", encoder, notNullValue());
ByteArrayOutputStream out = new ByteArrayOutputStream();
encoder.encode(value, out);
String hexEncoded = Hex.asHex(out.toByteArray());
assertThat("Encoded", hexEncoded, is(expectedEncodedHex));
}
@Test
public void testCoreEncoder_Boolean() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Boolean.class, Boolean.TRUE, "true");
}
@Test
public void testCoreEncoder_bool() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Boolean.TYPE, true, "true");
}
@Test
public void testCoreEncoder_Byte() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Byte.class, new Byte((byte) 0x21), "33");
}
@Test
public void testCoreEncoder_byte() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Byte.TYPE, (byte) 0x21, "33");
}
@Test
public void testCoreEncoder_Character() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Character.class, new Character('!'), "!");
}
@Test
public void testCoreEncoder_char() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Character.TYPE, '!', "!");
}
@Test
public void testCoreEncoder_Double() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Double.class, new Double(123.45), "123.45");
}
@Test
public void testCoreEncoder_double() throws IllegalAccessException, InstantiationException, EncodeException
{
//noinspection RedundantCast
assertTextEncoder(Double.TYPE, (double) 123.45, "123.45");
}
@Test
public void testCoreEncoder_Float() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Float.class, new Float(123.4567), "123.4567");
}
@Test
public void testCoreEncoder_float() throws IllegalAccessException, InstantiationException, EncodeException
{
//noinspection RedundantCast
assertTextEncoder(Float.TYPE, (float) 123.4567, "123.4567");
}
@Test
public void testCoreEncoder_Integer() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Integer.class, new Integer(123), "123");
}
@Test
public void testCoreEncoder_int() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Integer.TYPE, 123, "123");
}
@Test
public void testCoreEncoder_Long() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Long.class, new Long(123_456_789), "123456789");
}
@Test
public void testCoreEncoder_long() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Long.TYPE, 123_456_789L, "123456789");
}
@Test
public void testCoreEncoder_String() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(String.class, "Hello World", "Hello World");
}
@Test
public void testCoreEncoder_ByteBuffer() throws IllegalAccessException, InstantiationException, EncodeException
{
ByteBuffer buf = Hex.asByteBuffer("1122334455");
assertBinaryEncoder(ByteBuffer.class, buf, "1122334455");
}
@Test
public void testCoreEncoder_ByteArray() throws IllegalAccessException, InstantiationException, EncodeException
{
@ -211,63 +212,63 @@ public class AvailableEncodersTest
String expected = "99887766";
assertTextEncoder(Integer.class, val, expected);
}
@Test
public void testCustomEncoder_Time() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(TimeEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "12:34:56 GMT");
}
@Test
public void testCustomEncoder_Date() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(DateEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "2016.08.22");
}
@Test
public void testCustomEncoder_DateTime() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(DateTimeEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "2016.08.22 AD at 12:34:56 GMT");
}
@Test
public void testCustomEncoder_ValidDual_Text() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(ValidDualEncoder.class);
assertTextEncoder(Integer.class, 1234567, "[1,234,567]");
}
@Test
public void testCustomEncoder_ValidDual_Binary() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
@ -280,7 +281,7 @@ public class AvailableEncodersTest
public void testCustomEncoder_Register_Duplicate()
{
// has duplicated support for the same target Type
expectedException.expect(IllegalStateException.class);
expectedException.expect(InvalidWebSocketException.class);
expectedException.expectMessage(containsString("Duplicate"));
encoders.register(BadDualEncoder.class);
}
@ -290,9 +291,9 @@ public class AvailableEncodersTest
{
// Register DateEncoder (decodes java.util.Date)
encoders.register(DateEncoder.class);
// Register TimeEncoder (which also wants to decode java.util.Date)
expectedException.expect(IllegalStateException.class);
expectedException.expect(InvalidWebSocketException.class);
expectedException.expectMessage(containsString("Duplicate"));
encoders.register(TimeEncoder.class);
}

View File

@ -21,24 +21,33 @@ package org.eclipse.jetty.websocket.jsr356.function;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class JsrEndpointFunctions_OnMessage_BinaryTest
{
@ -50,6 +59,9 @@ public class JsrEndpointFunctions_OnMessage_BinaryTest
container = new ClientContainer();
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
private String expectedBuffer;
private AvailableEncoders encoders;
private AvailableDecoders decoders;
@ -64,6 +76,17 @@ public class JsrEndpointFunctions_OnMessage_BinaryTest
uriParams = new HashMap<>();
}
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnMessage_BinaryTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void assertOnMessageInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws Exception
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
@ -75,6 +98,9 @@ public class JsrEndpointFunctions_OnMessage_BinaryTest
endpointConfig
);
endpointFunctions.start();
// This invocation is the same for all tests
endpointFunctions.onOpen(newSession(socket));
assertThat("Has BinarySink", endpointFunctions.hasBinarySink(), is(true));
@ -100,6 +126,7 @@ public class JsrEndpointFunctions_OnMessage_BinaryTest
@Test
public void testInvokeMessage() throws Exception
{
expectedException.expect(InvalidSignatureException.class);
assertOnMessageInvocation(new MessageSocket(), "onMessage()");
}
@ -133,6 +160,7 @@ public class JsrEndpointFunctions_OnMessage_BinaryTest
@Test
public void testInvokeMessageSession() throws Exception
{
expectedException.expect(InvalidSignatureException.class);
assertOnMessageInvocation(new MessageSessionSocket(),
"onMessage(JsrSession[CLIENT,%s,DummyConnection])",
MessageSessionSocket.class.getName());

View File

@ -20,19 +20,25 @@ package org.eclipse.jetty.websocket.jsr356.function;
import static org.hamcrest.Matchers.containsString;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
@ -67,8 +73,19 @@ public class JsrEndpointFunctions_OnMessage_TextTest
decoders = new AvailableDecoders(endpointConfig);
uriParams = new HashMap<>();
}
private void onText(TrackingSocket socket, String msg)
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnMessage_TextTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void onText(TrackingSocket socket, String msg) throws Exception
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
@ -78,23 +95,28 @@ public class JsrEndpointFunctions_OnMessage_TextTest
uriParams,
endpointConfig
);
endpointFunctions.start();
// This invocation is the same for all tests
endpointFunctions.onOpen(newSession(socket));
ByteBuffer payload = BufferUtil.toBuffer(msg, StandardCharsets.UTF_8);
endpointFunctions.onText(payload, true);
}
private void assertOnMessageInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws InvocationTargetException, IllegalAccessException
private void assertOnMessageInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws Exception
{
onText(socket, "Hello World");
socket.assertEvent(String.format(expectedEventFormat, args));
}
@ClientEndpoint
public static class MessageSocket extends TrackingSocket
{
/**
* Invalid declaration - the type is ambiguous (is it TEXT / BINARY / PONG?)
*/
@SuppressWarnings("IncorrectOnMessageMethodsInspection")
@OnMessage
public void onMessage()
{
@ -103,14 +125,15 @@ public class JsrEndpointFunctions_OnMessage_TextTest
}
@Test
public void testAmbiguousEmptyMessage() throws InvocationTargetException, IllegalAccessException
public void testAmbiguousEmptyMessage() throws Exception
{
MessageSocket socket = new MessageSocket();
expectedException.expect(InvalidSignatureException.class);
expectedException.expectMessage(containsString("@OnMessage public void onMessage"));
onText(socket, "Hello World");
}
@ClientEndpoint
public static class MessageTextSocket extends TrackingSocket
{
@OnMessage
@ -121,15 +144,16 @@ public class JsrEndpointFunctions_OnMessage_TextTest
}
@Test
public void testInvokeMessageText() throws InvocationTargetException, IllegalAccessException
public void testInvokeMessageText() throws Exception
{
assertOnMessageInvocation(new MessageTextSocket(), "onMessage(Hello World)");
}
@ClientEndpoint
public static class MessageSessionSocket extends TrackingSocket
{
/**
* Invalid declaration - the type is ambiguous (is it TEXT / BINARY / PONG?)
* Invalid declaration - the type is ambiguous (is it TEXT, BINARY, or PONG?)
*/
@OnMessage
public void onMessage(Session session)
@ -139,7 +163,7 @@ public class JsrEndpointFunctions_OnMessage_TextTest
}
@Test
public void testAmbiguousMessageSession() throws InvocationTargetException, IllegalAccessException
public void testAmbiguousMessageSession() throws Exception
{
MessageSessionSocket socket = new MessageSessionSocket();
@ -147,19 +171,19 @@ public class JsrEndpointFunctions_OnMessage_TextTest
expectedException.expectMessage(containsString("@OnMessage public void onMessage"));
onText(socket, "Hello World");
}
@ClientEndpoint
public static class MessageSessionTextSocket extends TrackingSocket
{
@OnMessage
public void onMessage(Session session, String msg)
{
addEvent("onMessage(%s, %s)", session, msg);
}
}
@Test
public void testInvokeMessageSessionText() throws InvocationTargetException, IllegalAccessException
public void testInvokeMessageSessionText() throws Exception
{
assertOnMessageInvocation(new MessageSessionTextSocket(),
"onMessage(JsrSession[CLIENT,%s,DummyConnection], Hello World)",

View File

@ -62,7 +62,7 @@ public class JsrEndpointFunctions_OnOpenTest
decoders = new AvailableDecoders(endpointConfig);
uriParams = new HashMap<>();
}
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnOpenTest.class.getSimpleName();

View File

@ -161,9 +161,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (LOG.isDebugEnabled())
LOG.debug("starting - {}", this);
this.endpointFunctions = newEndpointFunctions(this.endpoint);
addBean(this.endpointFunctions);
Iterator<RemoteEndpointFactory> iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator();
if (iter.hasNext())
remoteEndpointFactory = iter.next();
@ -173,7 +170,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (LOG.isDebugEnabled())
LOG.debug("Using RemoteEndpointFactory: {}", remoteEndpointFactory);
this.endpointFunctions = newEndpointFunctions(this.endpoint);
addBean(this.endpointFunctions);
super.doStart();
}

View File

@ -481,17 +481,17 @@ public class CommonEndpointFunctions<T extends Session> extends AbstractLifeCycl
protected void clearOnPongFunction()
{
onPongFunction = null;
}
protected void clearOnTextSink()
{
onTextSink = null;
}
protected void clearOnBinarySink()
{
onBinarySink = null;
}
public BatchMode getBatchMode()
@ -650,8 +650,9 @@ public class CommonEndpointFunctions<T extends Session> extends AbstractLifeCycl
return;
StringBuilder err = new StringBuilder();
err.append("Cannot replace previously assigned ");
err.append("Cannot replace previously assigned [");
err.append(role);
err.append("] at ").append(describeOrigin(val));
err.append(" with ");
err.append(describeOrigin(origin));

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.websocket.common.message;
import java.nio.ByteBuffer;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
/**
* {@link Function} argument for Partial Binary Messages
*/
@ -31,15 +33,15 @@ public class PartialBinaryMessage
public PartialBinaryMessage(ByteBuffer payload, boolean fin)
{
this.payload = payload;
this.payload = payload == null ? BufferUtil.EMPTY_BUFFER : payload;
this.fin = fin;
}
public ByteBuffer getPayload()
{
return payload;
}
public boolean isFin()
{
return fin;

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.common;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
@ -72,7 +73,7 @@ public class AnnotatedEndpointDiscoverTest
{
// Should toss exception
thrown.expect(InvalidWebSocketException.class);
thrown.expectMessage(containsString("Cannot replace previously assigned BINARY Handler with "));
thrown.expectMessage(allOf(containsString("Cannot replace previously assigned"), containsString("BINARY Handler")));
createSession(new BadDuplicateBinarySocket());
}

View File

@ -38,6 +38,7 @@ public class DummyConnection implements LogicalConnection
private static final Logger LOG = Log.getLogger(DummyConnection.class);
private IOState iostate;
private WebSocketPolicy policy;
@Deprecated
public DummyConnection()