Fixes #425 - Removing duplicate Decoder affecting lifecycle

+ Duplicate Decoder was created and used in OnMessageCallable.
  Now using DecoderFactory to obtain already instantiated
  Decoder instead.
This commit is contained in:
Joakim Erdfelt 2016-06-20 17:47:29 -07:00
parent 02144e0a97
commit bcc57d161a
13 changed files with 202 additions and 107 deletions

View File

@ -171,5 +171,9 @@ public abstract class JsrCallable extends CallableMethod
} }
} }
public abstract void setDecoderClass(Class<? extends Decoder> decoderClass); /**
* The Type of Class a {@link Decoder} should be created to produce.
* @param decodingType the type of class a Decoder should be created to produce
*/
public abstract void setDecodingType(Class<?> decodingType);
} }

View File

@ -23,12 +23,9 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
import org.eclipse.jetty.websocket.jsr356.decoders.ByteArrayDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.ByteBufferDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.InputStreamDecoder;
/** /**
* Param handling for static Binary &#064;{@link OnMessage} parameters. * Param handling for static Binary &#064;{@link javax.websocket.OnMessage} parameters.
*/ */
public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
{ {
@ -46,14 +43,14 @@ public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
if (param.type.isAssignableFrom(ByteBuffer.class)) if (param.type.isAssignableFrom(ByteBuffer.class))
{ {
param.bind(Role.MESSAGE_BINARY); param.bind(Role.MESSAGE_BINARY);
callable.setDecoderClass(ByteBufferDecoder.class); callable.setDecodingType(ByteBuffer.class);
return true; return true;
} }
if (param.type.isAssignableFrom(byte[].class)) if (param.type.isAssignableFrom(byte[].class))
{ {
param.bind(Role.MESSAGE_BINARY); param.bind(Role.MESSAGE_BINARY);
callable.setDecoderClass(ByteArrayDecoder.class); callable.setDecodingType(byte[].class);
return true; return true;
} }
@ -62,7 +59,7 @@ public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_BINARY_STREAM); param.bind(Role.MESSAGE_BINARY_STREAM);
callable.setDecoderClass(InputStreamDecoder.class); callable.setDecodingType(InputStream.class);
return true; return true;
} }

View File

@ -23,7 +23,7 @@ import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata; import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
/** /**
* Param handling for Text or Binary &#064;{@link OnMessage} parameters declared as {@link Decoder}s * Param handling for Text or Binary &#064;{@link javax.websocket.OnMessage} parameters declared as {@link javax.websocket.Decoder}s
*/ */
public class JsrParamIdDecoder extends JsrParamIdOnMessage implements IJsrParamId public class JsrParamIdDecoder extends JsrParamIdOnMessage implements IJsrParamId
{ {
@ -67,7 +67,8 @@ public class JsrParamIdDecoder extends JsrParamIdOnMessage implements IJsrParamI
param.bind(Role.MESSAGE_PONG); param.bind(Role.MESSAGE_PONG);
break; break;
} }
callable.setDecoderClass(metadata.getCoderClass());
callable.setDecodingType(metadata.getObjectType());
return true; return true;
} }
return false; return false;

View File

@ -22,7 +22,6 @@ import javax.websocket.PongMessage;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
import org.eclipse.jetty.websocket.jsr356.decoders.PongMessageDecoder;
public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId
{ {
@ -41,7 +40,7 @@ public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_PONG); param.bind(Role.MESSAGE_PONG);
callable.setDecoderClass(PongMessageDecoder.class); callable.setDecodingType(PongMessage.class);
return true; return true;
} }
return false; return false;

View File

@ -22,19 +22,9 @@ import java.io.Reader;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
import org.eclipse.jetty.websocket.jsr356.decoders.BooleanDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.ByteDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.CharacterDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.DoubleDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.FloatDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.LongDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.ReaderDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.ShortDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.StringDecoder;
/** /**
* Param handling for static Text &#064;{@link OnMessage} parameters * Param handling for static Text &#064;{@link javax.websocket.OnMessage} parameters
*/ */
public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
{ {
@ -63,7 +53,7 @@ public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
if (param.type.isAssignableFrom(String.class)) if (param.type.isAssignableFrom(String.class))
{ {
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(StringDecoder.class); callable.setDecodingType(String.class);
return true; return true;
} }
@ -72,56 +62,56 @@ public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(BooleanDecoder.class); callable.setDecodingType(Boolean.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Byte.class) || (param.type == Byte.TYPE)) if (param.type.isAssignableFrom(Byte.class) || (param.type == Byte.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(ByteDecoder.class); callable.setDecodingType(Byte.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Character.class) || (param.type == Character.TYPE)) if (param.type.isAssignableFrom(Character.class) || (param.type == Character.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(CharacterDecoder.class); callable.setDecodingType(Character.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Double.class) || (param.type == Double.TYPE)) if (param.type.isAssignableFrom(Double.class) || (param.type == Double.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(DoubleDecoder.class); callable.setDecodingType(Double.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Float.class) || (param.type == Float.TYPE)) if (param.type.isAssignableFrom(Float.class) || (param.type == Float.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(FloatDecoder.class); callable.setDecodingType(Float.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Integer.class) || (param.type == Integer.TYPE)) if (param.type.isAssignableFrom(Integer.class) || (param.type == Integer.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(IntegerDecoder.class); callable.setDecodingType(Integer.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Long.class) || (param.type == Long.TYPE)) if (param.type.isAssignableFrom(Long.class) || (param.type == Long.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(LongDecoder.class); callable.setDecodingType(Long.class);
return true; return true;
} }
if (param.type.isAssignableFrom(Short.class) || (param.type == Short.TYPE)) if (param.type.isAssignableFrom(Short.class) || (param.type == Short.TYPE))
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(ShortDecoder.class); callable.setDecodingType(Short.class);
return true; return true;
} }
@ -130,7 +120,7 @@ public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
{ {
assertPartialMessageSupportDisabled(param,callable); assertPartialMessageSupportDisabled(param,callable);
param.bind(Role.MESSAGE_TEXT_STREAM); param.bind(Role.MESSAGE_TEXT_STREAM);
callable.setDecoderClass(ReaderDecoder.class); callable.setDecodingType(Reader.class);
return true; return true;
} }
@ -148,7 +138,7 @@ public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
else else
{ {
param.bind(Role.MESSAGE_TEXT); param.bind(Role.MESSAGE_TEXT);
callable.setDecoderClass(BooleanDecoder.class); callable.setDecodingType(Boolean.class);
} }
return true; return true;
} }

View File

@ -22,14 +22,13 @@ import java.lang.reflect.Method;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCodes; import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
/** /**
* Callable for {@link OnClose} annotated methods * Callable for {@link javax.websocket.OnClose} annotated methods
*/ */
public class OnCloseCallable extends JsrCallable public class OnCloseCallable extends JsrCallable
{ {
@ -82,7 +81,7 @@ public class OnCloseCallable extends JsrCallable
} }
@Override @Override
public void setDecoderClass(Class<? extends Decoder> decoderClass) public void setDecodingType(Class<?> decodingType)
{ {
/* ignore, not relevant for onClose */ /* ignore, not relevant for onClose */
} }

View File

@ -66,9 +66,8 @@ public class OnErrorCallable extends JsrCallable
} }
@Override @Override
public void setDecoderClass(Class<? extends Decoder> decoderClass) public void setDecodingType(Class<?> decodingType)
{ {
/* ignore, not relevant for onClose */ /* ignore, not relevant for onClose */
} }
} }

View File

@ -28,11 +28,11 @@ import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
/** /**
* Callable for {@link OnMessage} annotated methods with a whole or partial binary messages. * Callable for {@link javax.websocket.OnMessage} annotated methods with a whole or partial binary messages.
* <p> * <p>
* Not for use with {@link InputStream} based {@link OnMessage} method objects. * Not for use with {@link java.io.InputStream} based {@link javax.websocket.OnMessage} method objects.
* *
* @see Binary * @see javax.websocket.Decoder.Binary
*/ */
public class OnMessageBinaryCallable extends OnMessageCallable public class OnMessageBinaryCallable extends OnMessageCallable
{ {

View File

@ -26,7 +26,6 @@ import javax.websocket.Encoder;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.jsr356.EncoderFactory; import org.eclipse.jetty.websocket.jsr356.EncoderFactory;
import org.eclipse.jetty.websocket.jsr356.InitException;
import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
@ -34,7 +33,7 @@ public class OnMessageCallable extends JsrCallable
{ {
protected final Class<?> returnType; protected final Class<?> returnType;
protected Encoder returnEncoder; protected Encoder returnEncoder;
protected Class<? extends Decoder> decoderClass; protected Class<?> decodingType;
protected Decoder decoder; protected Decoder decoder;
protected int idxPartialMessageFlag = -1; protected int idxPartialMessageFlag = -1;
protected int idxMessageObject = -1; protected int idxMessageObject = -1;
@ -50,7 +49,7 @@ public class OnMessageCallable extends JsrCallable
{ {
super(copy); super(copy);
this.returnType = copy.returnType; this.returnType = copy.returnType;
this.decoderClass = copy.decoderClass; this.decodingType = copy.decodingType;
this.decoder = copy.decoder; this.decoder = copy.decoder;
this.idxPartialMessageFlag = copy.idxPartialMessageFlag; this.idxPartialMessageFlag = copy.idxPartialMessageFlag;
this.idxMessageObject = copy.idxMessageObject; this.idxMessageObject = copy.idxMessageObject;
@ -93,11 +92,6 @@ public class OnMessageCallable extends JsrCallable
return decoder; return decoder;
} }
public Class<? extends Decoder> getDecoderClass()
{
return decoderClass;
}
public Param getMessageObjectParam() public Param getMessageObjectParam()
{ {
if (idxMessageObject < 0) if (idxMessageObject < 0)
@ -138,16 +132,9 @@ public class OnMessageCallable extends JsrCallable
this.returnEncoder = encoderWrapper.getEncoder(); this.returnEncoder = encoderWrapper.getEncoder();
} }
if (decoderClass != null) if (decodingType != null)
{ {
try this.decoder = session.getDecoderFactory().getDecoderFor(decodingType);
{
this.decoder = decoderClass.newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
throw new InitException("Unable to create decoder: " + decoderClass.getName(),e);
}
} }
} }
@ -162,9 +149,9 @@ public class OnMessageCallable extends JsrCallable
} }
@Override @Override
public void setDecoderClass(Class<? extends Decoder> decoderClass) public void setDecodingType(Class<?> decodingType)
{ {
this.decoderClass = decoderClass; this.decodingType = decodingType;
messageRoleAssigned = true; messageRoleAssigned = true;
} }

View File

@ -27,11 +27,11 @@ import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
/** /**
* Callable for {@link OnMessage} annotated methods with a whole or partial text messages. * Callable for {@link javax.websocket.OnMessage} annotated methods with a whole or partial text messages.
* <p> * <p>
* Not for use with {@link Reader} based {@link OnMessage} method objects. * Not for use with {@link java.io.Reader} based {@link javax.websocket.OnMessage} method objects.
* *
* @see Text * @see javax.websocket.Decoder.Text
*/ */
public class OnMessageTextCallable extends OnMessageCallable public class OnMessageTextCallable extends OnMessageCallable
{ {

View File

@ -20,14 +20,13 @@ package org.eclipse.jetty.websocket.jsr356.annotations;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
/** /**
* Callable for {@link OnOpen} annotated methods * Callable for {@link javax.websocket.OnOpen} annotated methods
*/ */
public class OnOpenCallable extends JsrCallable public class OnOpenCallable extends JsrCallable
{ {
@ -62,7 +61,7 @@ public class OnOpenCallable extends JsrCallable
} }
@Override @Override
public void setDecoderClass(Class<? extends Decoder> decoderClass) public void setDecodingType(Class<?> decodingType)
{ {
/* ignore, not relevant for onClose */ /* ignore, not relevant for onClose */
} }

View File

@ -52,6 +52,7 @@ public class TimeDecoder implements Decoder.Text<Date>
@Override @Override
public void init(EndpointConfig config) public void init(EndpointConfig config)
{ {
System.out.println("#### INIT ####");
} }
@Override @Override

View File

@ -18,19 +18,28 @@
package org.eclipse.jetty.websocket.jsr356.server; package org.eclipse.jetty.websocket.jsr356.server;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension; import javax.websocket.Extension;
import javax.websocket.HandshakeResponse; import javax.websocket.HandshakeResponse;
import javax.websocket.OnMessage; import javax.websocket.OnMessage;
@ -98,8 +107,8 @@ public class ConfiguratorTest
@Override @Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{ {
super.modifyHandshake(sec,request,response); super.modifyHandshake(sec, request, response);
sec.getUserProperties().put("request-headers",request.getHeaders()); sec.getUserProperties().put("request-headers", request.getHeaders());
} }
} }
@ -113,7 +122,7 @@ public class ConfiguratorTest
response.append("Request Header [").append(headerKey).append("]: "); response.append("Request Header [").append(headerKey).append("]: ");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, List<String>> headers = (Map<String, List<String>>)session.getUserProperties().get("request-headers"); Map<String, List<String>> headers = (Map<String, List<String>>) session.getUserProperties().get("request-headers");
if (headers == null) if (headers == null)
{ {
response.append("<no headers found in session.getUserProperties()>"); response.append("<no headers found in session.getUserProperties()>");
@ -127,7 +136,7 @@ public class ConfiguratorTest
} }
else else
{ {
response.append(QuoteUtil.join(values,",")); response.append(QuoteUtil.join(values, ","));
} }
} }
@ -142,15 +151,15 @@ public class ConfiguratorTest
@Override @Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{ {
super.modifyHandshake(sec,request,response); super.modifyHandshake(sec, request, response);
} }
@Override @Override
public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
{ {
String seen = QuoteUtil.join(requested,","); String seen = QuoteUtil.join(requested, ",");
seenProtocols.compareAndSet(null,seen); seenProtocols.compareAndSet(null, seen);
return super.getNegotiatedSubprotocol(supported,requested); return super.getNegotiatedSubprotocol(supported, requested);
} }
} }
@ -175,15 +184,24 @@ public class ConfiguratorTest
{ {
int upgradeNum = upgradeCount.addAndGet(1); int upgradeNum = upgradeCount.addAndGet(1);
LOG.debug("Upgrade Num: {}", upgradeNum); LOG.debug("Upgrade Num: {}", upgradeNum);
sec.getUserProperties().put("upgradeNum",Integer.toString(upgradeNum)); sec.getUserProperties().put("upgradeNum", Integer.toString(upgradeNum));
switch(upgradeNum) { switch (upgradeNum)
case 1: sec.getUserProperties().put("apple", "fruit from tree"); break; {
case 2: sec.getUserProperties().put("blueberry", "fruit from bush"); break; case 1:
case 3: sec.getUserProperties().put("strawberry", "fruit from annual"); break; sec.getUserProperties().put("apple", "fruit from tree");
default: sec.getUserProperties().put("fruit"+upgradeNum, "placeholder"); break; break;
case 2:
sec.getUserProperties().put("blueberry", "fruit from bush");
break;
case 3:
sec.getUserProperties().put("strawberry", "fruit from annual");
break;
default:
sec.getUserProperties().put("fruit" + upgradeNum, "placeholder");
break;
} }
super.modifyHandshake(sec,request,response); super.modifyHandshake(sec, request, response);
} }
} }
@ -193,7 +211,7 @@ public class ConfiguratorTest
@OnMessage @OnMessage
public String onMessage(Session session, String msg) public String onMessage(Session session, String msg)
{ {
String value = (String)session.getUserProperties().get(msg); String value = (String) session.getUserProperties().get(msg);
StringBuilder response = new StringBuilder(); StringBuilder response = new StringBuilder();
response.append("Requested User Property: [").append(msg).append("] = "); response.append("Requested User Property: [").append(msg).append("] = ");
if (value == null) if (value == null)
@ -213,13 +231,13 @@ public class ConfiguratorTest
@Override @Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{ {
InetSocketAddress local = (InetSocketAddress)sec.getUserProperties().get(JsrCreator.PROP_LOCAL_ADDRESS); InetSocketAddress local = (InetSocketAddress) sec.getUserProperties().get(JsrCreator.PROP_LOCAL_ADDRESS);
InetSocketAddress remote = (InetSocketAddress)sec.getUserProperties().get(JsrCreator.PROP_REMOTE_ADDRESS); InetSocketAddress remote = (InetSocketAddress) sec.getUserProperties().get(JsrCreator.PROP_REMOTE_ADDRESS);
sec.getUserProperties().put("found.local", local); sec.getUserProperties().put("found.local", local);
sec.getUserProperties().put("found.remote", remote); sec.getUserProperties().put("found.remote", remote);
super.modifyHandshake(sec,request,response); super.modifyHandshake(sec, request, response);
} }
} }
@ -230,16 +248,16 @@ public class ConfiguratorTest
public String onMessage(Session session, String msg) public String onMessage(Session session, String msg)
{ {
StringBuilder response = new StringBuilder(); StringBuilder response = new StringBuilder();
appendPropValue(session,response,"javax.websocket.endpoint.localAddress"); appendPropValue(session, response, "javax.websocket.endpoint.localAddress");
appendPropValue(session,response,"javax.websocket.endpoint.remoteAddress"); appendPropValue(session, response, "javax.websocket.endpoint.remoteAddress");
appendPropValue(session,response,"found.local"); appendPropValue(session, response, "found.local");
appendPropValue(session,response,"found.remote"); appendPropValue(session, response, "found.remote");
return response.toString(); return response.toString();
} }
private void appendPropValue(Session session, StringBuilder response, String key) private void appendPropValue(Session session, StringBuilder response, String key)
{ {
InetSocketAddress value = (InetSocketAddress)session.getUserProperties().get(key); InetSocketAddress value = (InetSocketAddress) session.getUserProperties().get(key);
response.append("[").append(key).append("] = "); response.append("[").append(key).append("] = ");
response.append(toSafeAddr(value)); response.append(toSafeAddr(value));
@ -247,6 +265,84 @@ public class ConfiguratorTest
} }
} }
public static class SelectedProtocolConfigurator extends ServerEndpointConfig.Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response)
{
List<String> selectedProtocol = response.getHeaders().get("Sec-WebSocket-Protocol");
String protocol = "<>";
if (selectedProtocol != null || !selectedProtocol.isEmpty())
protocol = selectedProtocol.get(0);
config.getUserProperties().put("selected-subprotocol", protocol);
}
}
public static class GmtTimeDecoder implements Decoder.Text<Calendar>
{
private TimeZone TZ;
@Override
public Calendar decode(String s) throws DecodeException
{
if (TZ == null)
throw new DecodeException(s, ".init() not called");
try
{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TZ);
Date time = dateFormat.parse(s);
Calendar cal = Calendar.getInstance();
cal.setTimeZone(TZ);
cal.setTime(time);
return cal;
}
catch (ParseException e)
{
throw new DecodeException(s, "Unable to decode Time", e);
}
}
@Override
public void init(EndpointConfig config)
{
TZ = TimeZone.getTimeZone("GMT+0");
}
@Override
public void destroy()
{
}
@Override
public boolean willDecode(String s)
{
return true;
}
}
@ServerEndpoint(value = "/timedecoder",
subprotocols = { "time", "gmt" },
configurator = SelectedProtocolConfigurator.class,
decoders = {GmtTimeDecoder.class})
public static class TimeDecoderSocket
{
private TimeZone TZ = TimeZone.getTimeZone("GMT+0");
@OnMessage
public String onMessage(Calendar cal)
{
return String.format("cal=%s", newDateFormat().format(cal.getTime()));
}
private SimpleDateFormat newDateFormat()
{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss Z");
dateFormat.setTimeZone(TZ);
return dateFormat;
}
}
private static Server server; private static Server server;
private static URI baseServerUri; private static URI baseServerUri;
@ -269,6 +365,7 @@ public class ConfiguratorTest
container.addEndpoint(ProtocolsSocket.class); container.addEndpoint(ProtocolsSocket.class);
container.addEndpoint(UniqueUserPropsSocket.class); container.addEndpoint(UniqueUserPropsSocket.class);
container.addEndpoint(AddressSocket.class); container.addEndpoint(AddressSocket.class);
container.addEndpoint(TimeDecoderSocket.class);
server.start(); server.start();
String host = connector.getHost(); String host = connector.getHost();
@ -277,9 +374,9 @@ public class ConfiguratorTest
host = "localhost"; host = "localhost";
} }
int port = connector.getLocalPort(); int port = connector.getLocalPort();
baseServerUri = new URI(String.format("ws://%s:%d/",host,port)); baseServerUri = new URI(String.format("ws://%s:%d/", host, port));
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Server started on {}",baseServerUri); LOG.debug("Server started on {}", baseServerUri);
} }
public static String toSafeAddr(InetSocketAddress addr) public static String toSafeAddr(InetSocketAddress addr)
@ -288,7 +385,7 @@ public class ConfiguratorTest
{ {
return "<null>"; return "<null>";
} }
return String.format("%s:%d",addr.getAddress().getHostAddress(),addr.getPort()); return String.format("%s:%d", addr.getAddress().getHostAddress(), addr.getPort());
} }
@AfterClass @AfterClass
@ -308,7 +405,7 @@ public class ConfiguratorTest
client.connect(); client.connect();
client.sendStandardRequest(); client.sendStandardRequest();
HttpResponse response = client.readResponseHeader(); HttpResponse response = client.readResponseHeader();
Assert.assertThat("response.extensions",response.getExtensionsHeader(),is("identity")); Assert.assertThat("response.extensions", response.getExtensionsHeader(), is("identity"));
} }
} }
@ -323,7 +420,7 @@ public class ConfiguratorTest
client.connect(); client.connect();
client.sendStandardRequest(); client.sendStandardRequest();
HttpResponse response = client.readResponseHeader(); HttpResponse response = client.readResponseHeader();
Assert.assertThat("response.extensions",response.getExtensionsHeader(),nullValue()); Assert.assertThat("response.extensions", response.getExtensionsHeader(), nullValue());
} }
} }
@ -340,7 +437,7 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("X-Dummy")); client.write(new TextFrame().setPayload("X-Dummy"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\"")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\""));
} }
@ -359,7 +456,7 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("apple")); client.write(new TextFrame().setPayload("apple"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = \"fruit from tree\"")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = \"fruit from tree\""));
} }
@ -373,7 +470,7 @@ public class ConfiguratorTest
client.write(new TextFrame().setPayload("apple")); client.write(new TextFrame().setPayload("apple"));
client.write(new TextFrame().setPayload("blueberry")); client.write(new TextFrame().setPayload("blueberry"));
EventQueue<WebSocketFrame> frames = client.readFrames(2,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(2, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
// should have no value // should have no value
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = <null>")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = <null>"));
@ -399,7 +496,7 @@ public class ConfiguratorTest
InetSocketAddress expectedRemote = client.getRemoteSocketAddress(); InetSocketAddress expectedRemote = client.getRemoteSocketAddress();
client.write(new TextFrame().setPayload("addr")); client.write(new TextFrame().setPayload("addr"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
StringWriter expected = new StringWriter(); StringWriter expected = new StringWriter();
@ -407,8 +504,8 @@ public class ConfiguratorTest
// local <-> remote are opposite on server (duh) // local <-> remote are opposite on server (duh)
out.printf("[javax.websocket.endpoint.localAddress] = %s%n", toSafeAddr(expectedRemote)); out.printf("[javax.websocket.endpoint.localAddress] = %s%n", toSafeAddr(expectedRemote));
out.printf("[javax.websocket.endpoint.remoteAddress] = %s%n", toSafeAddr(expectedLocal)); out.printf("[javax.websocket.endpoint.remoteAddress] = %s%n", toSafeAddr(expectedLocal));
out.printf("[found.local] = %s%n",toSafeAddr(expectedRemote)); out.printf("[found.local] = %s%n", toSafeAddr(expectedRemote));
out.printf("[found.remote] = %s%n",toSafeAddr(expectedLocal)); out.printf("[found.remote] = %s%n", toSafeAddr(expectedLocal));
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is(expected.toString())); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is(expected.toString()));
} }
@ -431,7 +528,7 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("getProtocols")); client.write(new TextFrame().setPayload("getProtocols"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\"]")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\"]"));
} }
@ -454,7 +551,7 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("getProtocols")); client.write(new TextFrame().setPayload("getProtocols"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
} }
@ -477,7 +574,7 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("getProtocols")); client.write(new TextFrame().setPayload("getProtocols"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
} }
@ -500,9 +597,31 @@ public class ConfiguratorTest
client.expectUpgradeResponse(); client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("getProtocols")); client.write(new TextFrame().setPayload("getProtocols"));
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS); EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll(); WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
} }
} }
/**
* Test of Sec-WebSocket-Protocol, using non-spec case header
*/
@Test
public void testDecoderWithProtocol() throws Exception
{
URI uri = baseServerUri.resolve("/timedecoder");
try (BlockheadClient client = new BlockheadClient(uri))
{
client.addHeader("Sec-Websocket-Protocol: gmt\r\n");
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("2016-06-20T14:27:44"));
EventQueue<WebSocketFrame> frames = client.readFrames(1, 1, TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll();
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("cal=2016.06.20 AD at 14:27:44 +0000"));
}
}
} }