Merge remote-tracking branch 'origin/jetty-9.4.x'
This commit is contained in:
commit
4046ef26d3
48
VERSION.txt
48
VERSION.txt
|
@ -1,5 +1,53 @@
|
|||
jetty-10.0.0-SNAPSHOT
|
||||
|
||||
jetty-9.4.0.RC2 - 16 November 2016
|
||||
+ 240 Missing content for multipart request after upgrade to Jetty > 9.2.7
|
||||
+ 586 Thread pools and connectors
|
||||
+ 644 Modules for enabling logging
|
||||
+ 905 Jetty terminates SSL connections too early with Connection: close
|
||||
+ 907 Update to support apache jasper 8.5.5
|
||||
+ 1020 Java Util Logging properties in wrong location
|
||||
+ 1023 Error on configuring slf4j-simple-impl module
|
||||
+ 1029 Restore Request.setHttpVersion()
|
||||
+ 1031 Improve HttpField pre-encoding
|
||||
+ 1032 Remove jetty dependencies in jetty jasper classes
|
||||
+ 1037 Don't execute AsyncListener.onTimeout events in spare Scheduler-Thread
|
||||
+ 1038 AttributeNormalizer does not favor ${WAR} over other attributes, like
|
||||
${jetty.base}
|
||||
+ 1039 AttributeNormalizer should not track attributes that are null
|
||||
+ 1045 Abort HttpChannel onCompletion if wrong content length set
|
||||
+ 1046 Improve HTTP2Flusher error report
|
||||
+ 1050 Add multiple FilterHolder to a ServletContextHandler may cause problems
|
||||
+ 1054 Using WebSocketPingPongListener with empty PING payload results in
|
||||
NullPointerException
|
||||
+ 1057 Improve WebSocketUpgradeFilter fast path performance
|
||||
+ 1062 Jetty allows requests to hang under PUT load
|
||||
+ 1063 HostPortHttpField should handle port-only values
|
||||
+ 1064 HttpClient sets chunked transfer-encoding
|
||||
+ 1065 Response.setBufferSize checks for written content.
|
||||
+ 1066 Content-Length: 0 set when not required.
|
||||
+ 1067 Ensure if session scavenging is disabled, no candidate expired sessions
|
||||
accumulate
|
||||
+ 1069 Host header should be sent with HTTP/1.0
|
||||
+ 1070 Refactor calculation for session expiry
|
||||
+ 1071 Ensure dirty flag set on a new session
|
||||
+ 1072 InetAccessHandler needs InetAddress & Path based restrictions like
|
||||
IPAccessHandler did
|
||||
+ 1074 Improve HttpInput for non dispatched calls
|
||||
+ 1075 If read from session data cache fails, fallback to session data store
|
||||
+ 1076 bad error handling in
|
||||
ServerTimeoutsTest.testBlockingReadWithMinimumDataRateBelowLimit
|
||||
+ 1077 doHandle defined twice for ScopedHandler
|
||||
+ 1078 DigestAuthentication should use realm from server, even if unknown in
|
||||
advance
|
||||
+ 1081 DigestAuthenticator does not check the realm sent by the client
|
||||
+ 1091 Update to gcloud datastore 0.5.1
|
||||
+ 1098 MimeTypes.getCharsetFromContentType() unable parse "application/pdf;;;
|
||||
charset=UTF-8"
|
||||
+ 1099 PushCacheFilter pushes POST requests
|
||||
+ 1103 AbstractNCSARequestLog reports too much of the Request URI
|
||||
+ 480764 Suppress stacks in multipart filter tests
|
||||
|
||||
jetty-9.4.0.RC1 - 21 October 2016
|
||||
+ 277 Proxy servlet does not handle HTTP status 100 correctly
|
||||
+ 292 NPE in SslConnectionFactory newConnection
|
||||
|
|
|
@ -94,7 +94,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
if (BufferUtil.hasContent(buffer))
|
||||
{
|
||||
ByteBuffer upgradeBuffer = ByteBuffer.allocate(buffer.remaining());
|
||||
upgradeBuffer.put(buffer);
|
||||
upgradeBuffer.put(buffer).flip();
|
||||
return upgradeBuffer;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
@ -31,9 +35,6 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class HttpParserTest
|
||||
{
|
||||
/**
|
||||
|
@ -591,6 +592,28 @@ public class HttpParserTest
|
|||
Assert.assertEquals(0, _headers);
|
||||
Assert.assertEquals(null, _bad);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseBufferUpgradeFrom() throws Exception
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Connection: upgrade\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"Sec-WebSocket-Accept: 4GnyoUP4Sc1JD+2pCbNYAhFYVVA\r\n" +
|
||||
"\r\n" +
|
||||
"FOOGRADE");
|
||||
|
||||
HttpParser.ResponseHandler handler = new Handler();
|
||||
HttpParser parser = new HttpParser(handler);
|
||||
|
||||
while (!parser.isState(State.END))
|
||||
{
|
||||
parser.parseNext(buffer);
|
||||
}
|
||||
|
||||
assertThat(BufferUtil.toUTF8String(buffer), is("FOOGRADE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadMethodEncoding() throws Exception
|
||||
|
|
|
@ -116,7 +116,7 @@ public class HpackContext
|
|||
private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
|
||||
private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
|
||||
private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
|
||||
private static final int STATIC_SIZE = STATIC_TABLE.length-1;
|
||||
public static final int STATIC_SIZE = STATIC_TABLE.length-1;
|
||||
static
|
||||
{
|
||||
Set<String> added = new HashSet<>();
|
||||
|
|
|
@ -88,7 +88,7 @@ public class HpackEncoder
|
|||
private int _remoteMaxDynamicTableSize;
|
||||
private int _localMaxDynamicTableSize;
|
||||
private int _maxHeaderListSize;
|
||||
private int _size;
|
||||
private int _headerListSize;
|
||||
|
||||
public HpackEncoder()
|
||||
{
|
||||
|
@ -144,7 +144,7 @@ public class HpackEncoder
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("CtxTbl[%x] encoding",_context.hashCode()));
|
||||
|
||||
_size=0;
|
||||
_headerListSize=0;
|
||||
int pos = buffer.position();
|
||||
|
||||
// Check the dynamic table sizes!
|
||||
|
@ -179,9 +179,9 @@ public class HpackEncoder
|
|||
encode(buffer,field);
|
||||
|
||||
// Check size
|
||||
if (_maxHeaderListSize>0 && _size>_maxHeaderListSize)
|
||||
if (_maxHeaderListSize>0 && _headerListSize>_maxHeaderListSize)
|
||||
{
|
||||
LOG.warn("Header list size too large {} > {} for {}",_size,_maxHeaderListSize);
|
||||
LOG.warn("Header list size too large {} > {} for {}",_headerListSize,_maxHeaderListSize);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("metadata={}",metadata);
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ public class HpackEncoder
|
|||
field = new HttpField(field.getHeader(),field.getName(),"");
|
||||
|
||||
int field_size = field.getName().length() + field.getValue().length();
|
||||
_size+=field_size+32;
|
||||
_headerListSize+=field_size+32;
|
||||
|
||||
final int p=_debug?buffer.position():-1;
|
||||
|
||||
|
@ -307,9 +307,9 @@ public class HpackEncoder
|
|||
(huffman?"HuffV":"LitV")+
|
||||
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
|
||||
}
|
||||
else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
|
||||
else if (field_size>=_context.getMaxDynamicTableSize() || header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>2)
|
||||
{
|
||||
// Non indexed content length for 2 digits or more
|
||||
// Non indexed if field too large or a content length for 3 digits or more
|
||||
indexed=false;
|
||||
encodeName(buffer,(byte)0x00,4,header.asString(),name);
|
||||
encodeValue(buffer,true,field.getValue());
|
||||
|
@ -332,7 +332,8 @@ public class HpackEncoder
|
|||
// If we want the field referenced, then we add it to our
|
||||
// table and reference set.
|
||||
if (indexed)
|
||||
_context.add(field);
|
||||
if (_context.add(field)==null)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (_debug)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.eclipse.jetty.http2.hpack;
|
||||
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -29,6 +30,7 @@ import org.eclipse.jetty.http.HttpFields;
|
|||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
@ -185,6 +187,69 @@ public class HpackEncoderTest
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFieldLargerThanTable()
|
||||
{
|
||||
HttpFields fields = new HttpFields();
|
||||
|
||||
HpackEncoder encoder = new HpackEncoder(128);
|
||||
ByteBuffer buffer0 = BufferUtil.allocate(4096);
|
||||
int pos = BufferUtil.flipToFill(buffer0);
|
||||
encoder.encode(buffer0,new MetaData(HttpVersion.HTTP_2,fields));
|
||||
BufferUtil.flipToFlush(buffer0,pos);
|
||||
|
||||
encoder = new HpackEncoder(128);
|
||||
fields.add(new HttpField("user-agent","jetty/test"));
|
||||
ByteBuffer buffer1 = BufferUtil.allocate(4096);
|
||||
pos = BufferUtil.flipToFill(buffer1);
|
||||
encoder.encode(buffer1,new MetaData(HttpVersion.HTTP_2,fields));
|
||||
BufferUtil.flipToFlush(buffer1,pos);
|
||||
|
||||
encoder = new HpackEncoder(128);
|
||||
fields.add(new HttpField(":path",
|
||||
"This is a very large field, whose size is larger than the dynamic table so it should not be indexed as it will not fit in the table ever!"+
|
||||
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "+
|
||||
"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY "+
|
||||
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ "));
|
||||
ByteBuffer buffer2 = BufferUtil.allocate(4096);
|
||||
pos = BufferUtil.flipToFill(buffer2);
|
||||
encoder.encode(buffer2,new MetaData(HttpVersion.HTTP_2,fields));
|
||||
BufferUtil.flipToFlush(buffer2,pos);
|
||||
|
||||
encoder = new HpackEncoder(128);
|
||||
fields.add(new HttpField("host","somehost"));
|
||||
ByteBuffer buffer = BufferUtil.allocate(4096);
|
||||
pos = BufferUtil.flipToFill(buffer);
|
||||
encoder.encode(buffer,new MetaData(HttpVersion.HTTP_2,fields));
|
||||
BufferUtil.flipToFlush(buffer,pos);
|
||||
|
||||
//System.err.println(BufferUtil.toHexString(buffer0));
|
||||
//System.err.println(BufferUtil.toHexString(buffer1));
|
||||
//System.err.println(BufferUtil.toHexString(buffer2));
|
||||
//System.err.println(BufferUtil.toHexString(buffer));
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// check first field is static index name and dynamic index body
|
||||
assertThat((buffer.get(buffer0.remaining())&0xFF)>>6,equalTo(1));
|
||||
|
||||
// check first field is static index name and literal body
|
||||
assertThat((buffer.get(buffer1.remaining())&0xFF)>>4,equalTo(0));
|
||||
|
||||
// check first field is static index name and dynamic index body
|
||||
assertThat((buffer.get(buffer2.remaining())&0xFF)>>6,equalTo(1));
|
||||
|
||||
// Only first and third fields are put in the table
|
||||
HpackContext context = encoder.getHpackContext();
|
||||
assertThat(context.size(),equalTo(2));
|
||||
assertThat(context.get(HpackContext.STATIC_SIZE+1).getHttpField().getName(),equalTo("host"));
|
||||
assertThat(context.get(HpackContext.STATIC_SIZE+2).getHttpField().getName(),equalTo("user-agent"));
|
||||
assertThat(context.getDynamicTableSize(),equalTo(
|
||||
context.get(HpackContext.STATIC_SIZE+1).getSize()+context.get(HpackContext.STATIC_SIZE+2).getSize()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -456,6 +456,8 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
|
|||
public String toConnectionString()
|
||||
{
|
||||
Connection connection = getConnection();
|
||||
if (connection == null) // can happen during upgrade
|
||||
return "<null>";
|
||||
if (connection instanceof AbstractConnection)
|
||||
return ((AbstractConnection)connection).toConnectionString();
|
||||
return String.format("%s@%x",connection.getClass().getSimpleName(),connection.hashCode());
|
||||
|
|
|
@ -1126,12 +1126,10 @@ public class BufferUtil
|
|||
buf.append("\\x").append(TypeUtil.toHexString(b));
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Convert buffer to a Hex Summary String.
|
||||
* @param buffer the buffer to generate a hex byte summary from
|
||||
* @return A string showing the escaped content of the buffer around the
|
||||
* position and limit (marked with <<< and >>>)
|
||||
* @return A string showing a summary of the content in hex
|
||||
*/
|
||||
public static String toHexSummary(ByteBuffer buffer)
|
||||
{
|
||||
|
@ -1152,6 +1150,18 @@ public class BufferUtil
|
|||
return buf.toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Convert buffer to a Hex String.
|
||||
* @param buffer the buffer to generate a hex byte summary from
|
||||
* @return A hex string
|
||||
*/
|
||||
public static String toHexString(ByteBuffer buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
return "null";
|
||||
return TypeUtil.toHexString(toArray(buffer));
|
||||
}
|
||||
|
||||
|
||||
private final static int[] decDivisors =
|
||||
{1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
|
||||
|
|
|
@ -79,26 +79,35 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
|||
private final DecoderFactory decoderFactory;
|
||||
/** Tracking all primitive encoders for the container */
|
||||
private final EncoderFactory encoderFactory;
|
||||
/** The jetty websocket client in use for this container */
|
||||
private final WebSocketClient client;
|
||||
/** Tracking for all declared Client endpoints */
|
||||
private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
|
||||
/** The jetty websocket client in use for this container */
|
||||
private WebSocketClient client;
|
||||
|
||||
|
||||
/**
|
||||
* This is the entry point for {@link javax.websocket.ContainerProvider#getWebSocketContainer()}
|
||||
*/
|
||||
public ClientContainer()
|
||||
{
|
||||
// This constructor is used with Standalone JSR Client usage.
|
||||
this(new SimpleContainerScope(WebSocketPolicy.newClientPolicy()));
|
||||
client.setDaemon(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the entry point for ServerContainer, via ServletContext.getAttribute(ServerContainer.class.getName())
|
||||
*
|
||||
* @param scope the scope of the ServerContainer
|
||||
*/
|
||||
public ClientContainer(WebSocketContainerScope scope)
|
||||
{
|
||||
boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");
|
||||
|
||||
this.scopeDelegate = scope;
|
||||
client = new WebSocketClient(scope, new SslContextFactory(trustAll));
|
||||
client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
|
||||
client.setSessionFactory(new JsrSessionFactory(this));
|
||||
client = new WebSocketClient(scope,
|
||||
new JsrEventDriverFactory(scope.getPolicy()),
|
||||
new JsrSessionFactory(this));
|
||||
client.getSslContextFactory().setTrustAll(trustAll);
|
||||
addBean(client);
|
||||
|
||||
this.endpointClientMetadataCache = new ConcurrentHashMap<>();
|
||||
|
@ -116,7 +125,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
|||
ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
|
||||
ClientUpgradeRequest req = new ClientUpgradeRequest();
|
||||
UpgradeListener upgradeListener = null;
|
||||
|
||||
|
||||
for (Extension ext : config.getExtensions())
|
||||
{
|
||||
req.addExtensions(new JsrExtensionConfig(ext));
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jsr356;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -49,21 +48,7 @@ public class JsrUpgradeListener implements UpgradeListener
|
|||
configurator.beforeRequest(headers);
|
||||
|
||||
// Handle cookies
|
||||
for (String name : headers.keySet())
|
||||
{
|
||||
if ("cookie".equalsIgnoreCase(name))
|
||||
{
|
||||
List<String> values = headers.get(name);
|
||||
if (values != null)
|
||||
{
|
||||
for (String cookie : values)
|
||||
{
|
||||
List<HttpCookie> cookies = HttpCookie.parse(cookie);
|
||||
request.getCookies().addAll(cookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
request.setHeaders(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -85,6 +85,8 @@ public abstract class AnnotatedEndpointMetadata<T extends Annotation, C extends
|
|||
private final Class<?> endpointClass;
|
||||
private DecoderMetadataSet decoders;
|
||||
private EncoderMetadataSet encoders;
|
||||
private long maxTextMessageSize = -1;
|
||||
private long maxBinaryMessageSize = -1;
|
||||
|
||||
protected AnnotatedEndpointMetadata(Class<?> endpointClass)
|
||||
{
|
||||
|
@ -119,7 +121,19 @@ public abstract class AnnotatedEndpointMetadata<T extends Annotation, C extends
|
|||
public abstract T getAnnotation();
|
||||
|
||||
public abstract C getConfig();
|
||||
|
||||
|
||||
@Override
|
||||
public long maxBinaryMessageSize()
|
||||
{
|
||||
return maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long maxTextMessageSize()
|
||||
{
|
||||
return maxTextMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderMetadataSet getDecoders()
|
||||
{
|
||||
|
@ -137,4 +151,14 @@ public abstract class AnnotatedEndpointMetadata<T extends Annotation, C extends
|
|||
{
|
||||
return endpointClass;
|
||||
}
|
||||
|
||||
public void setMaxBinaryMessageSize(long maxBinaryMessageSize)
|
||||
{
|
||||
this.maxBinaryMessageSize = maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxTextMessageSize(long maxTextMessageSize)
|
||||
{
|
||||
this.maxTextMessageSize = maxTextMessageSize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,20 +134,25 @@ public class AnnotatedEndpointScanner<T extends Annotation, C extends EndpointCo
|
|||
OnMessageCallable onmessage = new OnMessageCallable(pojo,method);
|
||||
visitMethod(onmessage,pojo,method,paramsOnMessage,OnMessage.class);
|
||||
|
||||
OnMessage messageAnno = (OnMessage) annotation;
|
||||
Param param = onmessage.getMessageObjectParam();
|
||||
switch (param.role)
|
||||
{
|
||||
case MESSAGE_BINARY:
|
||||
metadata.onBinary = new OnMessageBinaryCallable(onmessage);
|
||||
metadata.setMaxBinaryMessageSize(messageAnno.maxMessageSize());
|
||||
break;
|
||||
case MESSAGE_BINARY_STREAM:
|
||||
metadata.onBinaryStream = new OnMessageBinaryStreamCallable(onmessage);
|
||||
metadata.setMaxBinaryMessageSize(messageAnno.maxMessageSize());
|
||||
break;
|
||||
case MESSAGE_TEXT:
|
||||
metadata.onText = new OnMessageTextCallable(onmessage);
|
||||
metadata.setMaxTextMessageSize(messageAnno.maxMessageSize());
|
||||
break;
|
||||
case MESSAGE_TEXT_STREAM:
|
||||
metadata.onTextStream = new OnMessageTextStreamCallable(onmessage);
|
||||
metadata.setMaxTextMessageSize(messageAnno.maxMessageSize());
|
||||
break;
|
||||
case MESSAGE_PONG:
|
||||
metadata.onPong = new OnMessagePongCallable(onmessage);
|
||||
|
|
|
@ -21,13 +21,11 @@ package org.eclipse.jetty.websocket.jsr356.client;
|
|||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.OnMessage;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
|
||||
import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
|
||||
import org.eclipse.jetty.websocket.jsr356.annotations.OnMessageCallable;
|
||||
import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
|
||||
import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
|
||||
|
||||
|
@ -49,8 +47,8 @@ public class JsrClientEndpointImpl implements EventDriverImpl
|
|||
JsrEvents<ClientEndpoint, ClientEndpointConfig> events = new JsrEvents<>(metadata);
|
||||
|
||||
// Handle @OnMessage maxMessageSizes
|
||||
int maxBinaryMessage = getMaxMessageSize(policy.getMaxBinaryMessageSize(),metadata.onBinary,metadata.onBinaryStream);
|
||||
int maxTextMessage = getMaxMessageSize(policy.getMaxTextMessageSize(),metadata.onText,metadata.onTextStream);
|
||||
int maxBinaryMessage = getMaxMessageSize(policy.getMaxBinaryMessageSize(),metadata.maxBinaryMessageSize());
|
||||
int maxTextMessage = getMaxMessageSize(policy.getMaxTextMessageSize(),metadata.maxTextMessageSize());
|
||||
|
||||
policy.setMaxBinaryMessageSize(maxBinaryMessage);
|
||||
policy.setMaxTextMessageSize(maxTextMessage);
|
||||
|
@ -64,23 +62,11 @@ public class JsrClientEndpointImpl implements EventDriverImpl
|
|||
return "class is annotated with @" + ClientEndpoint.class.getName();
|
||||
}
|
||||
|
||||
private int getMaxMessageSize(int defaultMaxMessageSize, OnMessageCallable... onMessages)
|
||||
private int getMaxMessageSize(int defaultMaxMessageSize, long maxMessageSize)
|
||||
{
|
||||
for (OnMessageCallable callable : onMessages)
|
||||
if (maxMessageSize >= 1)
|
||||
{
|
||||
if (callable == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
OnMessage onMsg = callable.getMethod().getAnnotation(OnMessage.class);
|
||||
if (onMsg == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (onMsg.maxMessageSize() > 0)
|
||||
{
|
||||
return (int)onMsg.maxMessageSize();
|
||||
}
|
||||
return (int) maxMessageSize;
|
||||
}
|
||||
return defaultMaxMessageSize;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.eclipse.jetty.websocket.jsr356.JsrSession;
|
|||
import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
|
||||
import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialOnMessage;
|
||||
import org.eclipse.jetty.websocket.jsr356.messages.TextPartialOnMessage;
|
||||
import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
|
||||
|
||||
/**
|
||||
* Base implementation for JSR-356 Annotated event drivers.
|
||||
|
@ -54,6 +55,13 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
|
|||
{
|
||||
super(policy,endpointInstance);
|
||||
this.events = events;
|
||||
|
||||
EndpointMetadata metadata = endpointInstance.getMetadata();
|
||||
|
||||
if (metadata.maxTextMessageSize() >= 1)
|
||||
policy.setMaxTextMessageSize((int) metadata.maxTextMessageSize());
|
||||
if (metadata.maxBinaryMessageSize() >= 1)
|
||||
policy.setMaxBinaryMessageSize((int) metadata.maxBinaryMessageSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,9 +20,13 @@ package org.eclipse.jetty.websocket.jsr356.metadata;
|
|||
|
||||
public interface EndpointMetadata
|
||||
{
|
||||
public DecoderMetadataSet getDecoders();
|
||||
DecoderMetadataSet getDecoders();
|
||||
|
||||
public EncoderMetadataSet getEncoders();
|
||||
|
||||
public Class<?> getEndpointClass();
|
||||
EncoderMetadataSet getEncoders();
|
||||
|
||||
Class<?> getEndpointClass();
|
||||
|
||||
default long maxTextMessageSize() { return -1; }
|
||||
|
||||
default long maxBinaryMessageSize() { return -1; }
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ public class AnnotatedEchoTest
|
|||
private static EchoHandler handler;
|
||||
private static URI serverUri;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@BeforeClass
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jsr356;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
|
@ -73,14 +77,15 @@ public class CookiesTest
|
|||
final String cookieName = "name";
|
||||
final String cookieValue = "value";
|
||||
final String cookieString = cookieName + "=" + cookieValue;
|
||||
|
||||
startServer(new EchoHandler()
|
||||
{
|
||||
@Override
|
||||
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response)
|
||||
{
|
||||
List<HttpCookie> cookies = request.getCookies();
|
||||
Assert.assertNotNull(cookies);
|
||||
Assert.assertEquals(1, cookies.size());
|
||||
assertThat("Cookies", cookies, notNullValue());
|
||||
assertThat("Cookies", cookies.size(), is(1));
|
||||
HttpCookie cookie = cookies.get(0);
|
||||
Assert.assertEquals(cookieName, cookie.getName());
|
||||
Assert.assertEquals(cookieValue, cookie.getValue());
|
||||
|
|
|
@ -42,12 +42,7 @@ public class AnnotatedRuntimeOnOpen
|
|||
@OnOpen
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
// Intentional runtime exception.
|
||||
int[] arr = new int[5];
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
arr[i] = 222;
|
||||
}
|
||||
throw new RuntimeException("Intentionally Misbehaving");
|
||||
}
|
||||
|
||||
@OnClose
|
||||
|
|
|
@ -38,12 +38,7 @@ public class EndpointRuntimeOnOpen extends Endpoint
|
|||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
// Intentional runtime exception.
|
||||
int[] arr = new int[5];
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
arr[i] = 222;
|
||||
}
|
||||
throw new RuntimeException("Intentionally Misbehaving");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,11 +22,11 @@ import static org.hamcrest.Matchers.instanceOf;
|
|||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -37,14 +37,20 @@ import org.eclipse.jetty.websocket.common.WebSocketSession;
|
|||
import org.eclipse.jetty.websocket.jsr356.EchoHandler;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class MisbehavingClassTest
|
||||
{
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
private static Server server;
|
||||
private static EchoHandler handler;
|
||||
private static URI serverUri;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@BeforeClass
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
|
@ -84,6 +90,7 @@ public class MisbehavingClassTest
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Test
|
||||
public void testEndpointRuntimeOnOpen() throws Exception
|
||||
{
|
||||
|
@ -92,19 +99,20 @@ public class MisbehavingClassTest
|
|||
|
||||
try (StacklessLogging logging = new StacklessLogging(EndpointRuntimeOnOpen.class, WebSocketSession.class))
|
||||
{
|
||||
// expecting ArrayIndexOutOfBoundsException during onOpen
|
||||
Session session = container.connectToServer(socket,serverUri);
|
||||
// expecting IOException during onOpen
|
||||
expectedException.expect(IOException.class);
|
||||
expectedException.expectCause(instanceOf(RuntimeException.class));
|
||||
container.connectToServer(socket, serverUri);
|
||||
expectedException.reportMissingExceptionWithMessage("Should have failed .connectToServer()");
|
||||
|
||||
assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
|
||||
// technically, the session object isn't invalid here.
|
||||
assertThat("Session.isOpen",session.isOpen(),is(false));
|
||||
assertThat("Should have only had 1 error",socket.errors.size(),is(1));
|
||||
|
||||
Throwable cause = socket.errors.pop();
|
||||
assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
|
||||
assertThat("Error",cause,instanceOf(RuntimeException.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Test
|
||||
public void testAnnotatedRuntimeOnOpen() throws Exception
|
||||
{
|
||||
|
@ -113,14 +121,14 @@ public class MisbehavingClassTest
|
|||
|
||||
try (StacklessLogging logging = new StacklessLogging(AnnotatedRuntimeOnOpen.class, WebSocketSession.class))
|
||||
{
|
||||
// expecting ArrayIndexOutOfBoundsException during onOpen
|
||||
Session session = container.connectToServer(socket,serverUri);
|
||||
// expecting IOException during onOpen
|
||||
expectedException.expect(IOException.class);
|
||||
expectedException.expectCause(instanceOf(RuntimeException.class));
|
||||
container.connectToServer(socket, serverUri);
|
||||
expectedException.reportMissingExceptionWithMessage("Should have failed .connectToServer()");
|
||||
|
||||
assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
|
||||
// technically, the session object isn't invalid here.
|
||||
assertThat("Session.isOpen",session.isOpen(),is(false));
|
||||
assertThat("Should have only had 1 error",socket.errors.size(),is(1));
|
||||
|
||||
Throwable cause = socket.errors.pop();
|
||||
assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class AnnotatedServerEndpointMetadata extends AnnotatedEndpointMetadata<S
|
|||
this.endpoint = anno;
|
||||
this.config = new AnnotatedServerEndpointConfig(containerScope,websocket,anno,baseConfig);
|
||||
|
||||
getDecoders().addAll(anno.decoders());
|
||||
getDecoders().addAll(anno.decoders());
|
||||
getEncoders().addAll(anno.encoders());
|
||||
}
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ public class ConfiguratorTest
|
|||
{
|
||||
List<String> selectedProtocol = response.getHeaders().get("Sec-WebSocket-Protocol");
|
||||
String protocol = "<>";
|
||||
if (selectedProtocol != null || !selectedProtocol.isEmpty())
|
||||
if (selectedProtocol != null && !selectedProtocol.isEmpty())
|
||||
protocol = selectedProtocol.get(0);
|
||||
config.getUserProperties().put("selected-subprotocol", protocol);
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.io.Closeable;
|
|||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
|
||||
/**
|
||||
* Session represents an active link of communications with a Remote WebSocket Endpoint.
|
||||
*/
|
||||
|
|
|
@ -21,342 +21,307 @@ package org.eclipse.jetty.websocket.api;
|
|||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
|
||||
public class UpgradeRequest
|
||||
/**
|
||||
* The HTTP Upgrade to WebSocket Request
|
||||
*/
|
||||
public interface UpgradeRequest
|
||||
{
|
||||
private URI requestURI;
|
||||
private List<String> subProtocols = new ArrayList<>(1);
|
||||
private List<ExtensionConfig> extensions = new ArrayList<>(1);
|
||||
private List<HttpCookie> cookies = new ArrayList<>(1);
|
||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private Map<String, List<String>> parameters = new HashMap<>(1);
|
||||
private Object session;
|
||||
private String httpVersion;
|
||||
private String method;
|
||||
private String host;
|
||||
private boolean secure;
|
||||
/**
|
||||
* Add WebSocket Extension Configuration(s) to Upgrade Request.
|
||||
* <p>
|
||||
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||
* negotiated
|
||||
*
|
||||
* @param configs the configuration(s) to add
|
||||
*/
|
||||
void addExtensions(ExtensionConfig... configs);
|
||||
|
||||
protected UpgradeRequest()
|
||||
{
|
||||
/* anonymous, no requestURI, upgrade request */
|
||||
}
|
||||
/**
|
||||
* Add WebSocket Extension Configuration(s) to request
|
||||
* <p>
|
||||
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||
* negotiated
|
||||
*
|
||||
* @param configs the configuration(s) to add
|
||||
*/
|
||||
void addExtensions(String... configs);
|
||||
|
||||
public UpgradeRequest(String requestURI)
|
||||
{
|
||||
this(URI.create(requestURI));
|
||||
}
|
||||
/**
|
||||
* Remove all headers from request.
|
||||
* @deprecated (no longer supported, as this can undo the required upgrade request headers)
|
||||
*/
|
||||
@Deprecated
|
||||
void clearHeaders();
|
||||
|
||||
public UpgradeRequest(URI requestURI)
|
||||
{
|
||||
setRequestURI(requestURI);
|
||||
}
|
||||
/**
|
||||
* Get the list of Cookies on the Upgrade request
|
||||
*
|
||||
* @return the list of Cookies
|
||||
*/
|
||||
List<HttpCookie> getCookies();
|
||||
|
||||
public void addExtensions(ExtensionConfig... configs)
|
||||
{
|
||||
Collections.addAll(extensions, configs);
|
||||
}
|
||||
/**
|
||||
* Get the list of WebSocket Extension Configurations for this Upgrade Request.
|
||||
* <p>
|
||||
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||
* negotiated
|
||||
*
|
||||
* @return the list of Extension configurations (in the order they were specified)
|
||||
*/
|
||||
List<ExtensionConfig> getExtensions();
|
||||
|
||||
public void addExtensions(String... configs)
|
||||
{
|
||||
for (String config : configs)
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(config));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a specific Header value from Upgrade Request
|
||||
*
|
||||
* @param name the name of the header
|
||||
* @return the value of the header (null if header does not exist)
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
public void clearHeaders()
|
||||
{
|
||||
headers.clear();
|
||||
}
|
||||
/**
|
||||
* Get the specific Header value, as an <code>int</code>, from the Upgrade Request.
|
||||
*
|
||||
* @param name the name of the header
|
||||
* @return the value of the header as an <code>int</code> (-1 if header does not exist)
|
||||
* @throws NumberFormatException if unable to parse value as an int.
|
||||
*/
|
||||
int getHeaderInt(String name);
|
||||
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
/**
|
||||
* Get the headers as a Map of keys to value lists.
|
||||
*
|
||||
* @return the headers
|
||||
*/
|
||||
Map<String, List<String>> getHeaders();
|
||||
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
/**
|
||||
* Get the specific header values (for multi-value headers)
|
||||
*
|
||||
* @param name the header name
|
||||
* @return the value list (null if no header exists)
|
||||
*/
|
||||
List<String> getHeaders(String name);
|
||||
|
||||
public String getHeader(String name)
|
||||
{
|
||||
List<String> values = headers.get(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return values.get(0);
|
||||
}
|
||||
// join it with commas
|
||||
boolean needsDelim = false;
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (String value : values)
|
||||
{
|
||||
if (needsDelim)
|
||||
{
|
||||
ret.append(", ");
|
||||
}
|
||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||
needsDelim = true;
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
/**
|
||||
* The host of the Upgrade Request URI
|
||||
* <p>
|
||||
* Equivalent to {@link #getRequestURI()#getHost()}
|
||||
*
|
||||
* @return host of the request URI
|
||||
*/
|
||||
String getHost();
|
||||
|
||||
public int getHeaderInt(String name)
|
||||
{
|
||||
List<String> values = headers.get(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return Integer.parseInt(values.get(0));
|
||||
}
|
||||
throw new NumberFormatException("Cannot convert multi-value header into int");
|
||||
}
|
||||
/**
|
||||
* The HTTP version used for this Upgrade Request
|
||||
* <p>
|
||||
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always
|
||||
* <code>HTTP/1.1</code>
|
||||
*
|
||||
* @return the HTTP Version used
|
||||
*/
|
||||
String getHttpVersion();
|
||||
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
/**
|
||||
* The HTTP method for this Upgrade Request.
|
||||
* <p>
|
||||
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always <code>GET</code>
|
||||
*
|
||||
* @return the HTTP method used
|
||||
*/
|
||||
String getMethod();
|
||||
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
public String getHttpVersion()
|
||||
{
|
||||
return httpVersion;
|
||||
}
|
||||
|
||||
public String getMethod()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getOrigin()
|
||||
{
|
||||
return getHeader("Origin");
|
||||
}
|
||||
/**
|
||||
* The WebSocket Origin of this Upgrade Request
|
||||
* <p>
|
||||
* See <a href="http://tools.ietf.org/html/rfc6455#section-10.2">RFC6455: Section 10.2</a> for details.
|
||||
* <p>
|
||||
* Equivalent to {@link #getHeader("Origin")}
|
||||
*
|
||||
* @return the Origin header
|
||||
*/
|
||||
String getOrigin();
|
||||
|
||||
/**
|
||||
* Returns a map of the query parameters of the request.
|
||||
*
|
||||
*
|
||||
* @return a unmodifiable map of query parameters of the request.
|
||||
*/
|
||||
public Map<String, List<String>> getParameterMap()
|
||||
{
|
||||
return Collections.unmodifiableMap(parameters);
|
||||
}
|
||||
Map<String, List<String>> getParameterMap();
|
||||
|
||||
public String getProtocolVersion()
|
||||
{
|
||||
String version = getHeader("Sec-WebSocket-Version");
|
||||
if (version == null)
|
||||
{
|
||||
return "13"; // Default
|
||||
}
|
||||
return version;
|
||||
}
|
||||
/**
|
||||
* Get the WebSocket Protocol Version
|
||||
* <p>
|
||||
* As of <a href="http://tools.ietf.org/html/rfc6455#section-11.6">RFC6455</a>, Jetty only supports version
|
||||
* <code>13</code>
|
||||
*
|
||||
* @return the WebSocket protocol version
|
||||
*/
|
||||
String getProtocolVersion();
|
||||
|
||||
public String getQueryString()
|
||||
{
|
||||
return requestURI.getQuery();
|
||||
}
|
||||
/**
|
||||
* Get the Query String of the request URI.
|
||||
* <p>
|
||||
* Equivalent to {@link #getRequestURI()#getQueryString()}
|
||||
*
|
||||
* @return the request uri query string
|
||||
*/
|
||||
String getQueryString();
|
||||
|
||||
public URI getRequestURI()
|
||||
{
|
||||
return requestURI;
|
||||
}
|
||||
/**
|
||||
* Get the Request URI
|
||||
*
|
||||
* @return the request URI
|
||||
*/
|
||||
URI getRequestURI();
|
||||
|
||||
/**
|
||||
* Access the Servlet HTTP Session (if present)
|
||||
* <p>
|
||||
* Note: Never present on a Client UpgradeRequest.
|
||||
*
|
||||
*
|
||||
* @return the Servlet HTTPSession on server side UpgradeRequests
|
||||
*/
|
||||
public Object getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
Object getSession();
|
||||
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
return subProtocols;
|
||||
}
|
||||
/**
|
||||
* Get the list of offered WebSocket sub-protocols.
|
||||
*
|
||||
* @return the list of offered sub-protocols
|
||||
*/
|
||||
List<String> getSubProtocols();
|
||||
|
||||
/**
|
||||
* Get the User Principal for this request.
|
||||
* <p>
|
||||
* Only applicable when using UpgradeRequest from server side.
|
||||
*
|
||||
*
|
||||
* @return the user principal
|
||||
*/
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
// Server side should override to implement
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasSubProtocol(String test)
|
||||
{
|
||||
for (String protocol : subProtocols)
|
||||
{
|
||||
if (protocol.equalsIgnoreCase(test))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isOrigin(String test)
|
||||
{
|
||||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
public boolean isSecure()
|
||||
{
|
||||
return secure;
|
||||
}
|
||||
|
||||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
this.cookies.clear();
|
||||
if (cookies != null && !cookies.isEmpty())
|
||||
{
|
||||
this.cookies.addAll(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
this.extensions.clear();
|
||||
if (configs != null)
|
||||
{
|
||||
this.extensions.addAll(configs);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHeader(String name, List<String> values)
|
||||
{
|
||||
headers.put(name,values);
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
setHeader(name,values);
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, List<String>> headers)
|
||||
{
|
||||
clearHeaders();
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
||||
{
|
||||
String name = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
setHeader(name,values);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHttpVersion(String httpVersion)
|
||||
{
|
||||
this.httpVersion = httpVersion;
|
||||
}
|
||||
|
||||
public void setMethod(String method)
|
||||
{
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
protected void setParameterMap(Map<String, List<String>> parameters)
|
||||
{
|
||||
this.parameters.clear();
|
||||
this.parameters.putAll(parameters);
|
||||
}
|
||||
|
||||
public void setRequestURI(URI uri)
|
||||
{
|
||||
this.requestURI = uri;
|
||||
String scheme = uri.getScheme();
|
||||
if ("ws".equalsIgnoreCase(scheme))
|
||||
{
|
||||
secure = false;
|
||||
}
|
||||
else if ("wss".equalsIgnoreCase(scheme))
|
||||
{
|
||||
secure = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
|
||||
}
|
||||
this.host = this.requestURI.getHost();
|
||||
this.parameters.clear();
|
||||
}
|
||||
|
||||
public void setSession(Object session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void setSubProtocols(List<String> subProtocols)
|
||||
{
|
||||
this.subProtocols.clear();
|
||||
if (subProtocols != null)
|
||||
{
|
||||
this.subProtocols.addAll(subProtocols);
|
||||
}
|
||||
}
|
||||
Principal getUserPrincipal();
|
||||
|
||||
/**
|
||||
* Set Sub Protocol request list.
|
||||
*
|
||||
* @param protocols
|
||||
* the sub protocols desired
|
||||
* Test if a specific sub-protocol is offered
|
||||
*
|
||||
* @param test the sub-protocol to test for
|
||||
* @return true if sub-protocol exists on request
|
||||
*/
|
||||
public void setSubProtocols(String... protocols)
|
||||
{
|
||||
subProtocols.clear();
|
||||
Collections.addAll(subProtocols, protocols);
|
||||
}
|
||||
boolean hasSubProtocol(String test);
|
||||
|
||||
/**
|
||||
* Test if supplied Origin is the same as the Request
|
||||
*
|
||||
* @param test the supplied origin
|
||||
* @return true if the supplied origin matches the request origin
|
||||
*/
|
||||
boolean isOrigin(String test);
|
||||
|
||||
/**
|
||||
* Test if connection is secure.
|
||||
*
|
||||
* @return true if connection is secure.
|
||||
*/
|
||||
boolean isSecure();
|
||||
|
||||
/**
|
||||
* Set the list of Cookies on the request
|
||||
*
|
||||
* @param cookies the cookies to use
|
||||
*/
|
||||
void setCookies(List<HttpCookie> cookies);
|
||||
|
||||
/**
|
||||
* Set the list of WebSocket Extension configurations on the request.
|
||||
* @param configs the list of extension configurations
|
||||
*/
|
||||
void setExtensions(List<ExtensionConfig> configs);
|
||||
|
||||
/**
|
||||
* Set a specific header with multi-value field
|
||||
* <p>
|
||||
* Overrides any previous value for this named header
|
||||
*
|
||||
* @param name the name of the header
|
||||
* @param values the multi-value field
|
||||
*/
|
||||
void setHeader(String name, List<String> values);
|
||||
|
||||
/**
|
||||
* Set a specific header value
|
||||
* <p>
|
||||
* Overrides any previous value for this named header
|
||||
*
|
||||
* @param name the header to set
|
||||
* @param value the value to set it to
|
||||
*/
|
||||
void setHeader(String name, String value);
|
||||
|
||||
/**
|
||||
* Sets multiple headers on the request.
|
||||
* <p>
|
||||
* Only sets those headers provided, does not remove
|
||||
* headers that exist on request and are not provided in the
|
||||
* parameter for this method.
|
||||
* <p>
|
||||
* Convenience method vs calling {@link #setHeader(String, List)} multiple times.
|
||||
*
|
||||
* @param headers the headers to set
|
||||
*/
|
||||
void setHeaders(Map<String, List<String>> headers);
|
||||
|
||||
/**
|
||||
* Set the HTTP Version to use.
|
||||
* <p>
|
||||
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this should always be
|
||||
* <code>HTTP/1.1</code>
|
||||
*
|
||||
* @param httpVersion the HTTP version to use.
|
||||
*/
|
||||
void setHttpVersion(String httpVersion);
|
||||
|
||||
/**
|
||||
* Set the HTTP method to use.
|
||||
* <p>
|
||||
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always <code>GET</code>
|
||||
*
|
||||
* @param method the HTTP method to use.
|
||||
*/
|
||||
void setMethod(String method);
|
||||
|
||||
/**
|
||||
* Set the Request URI to use for this request.
|
||||
* <p>
|
||||
* Must be an absolute URI with scheme <code>'ws'</code> or <code>'wss'</code>
|
||||
*
|
||||
* @param uri the Request URI
|
||||
*/
|
||||
void setRequestURI(URI uri);
|
||||
|
||||
/**
|
||||
* Set the Session associated with this request.
|
||||
* <p>
|
||||
* Typically used to associate the Servlet HttpSession object.
|
||||
*
|
||||
* @param session the session object to associate with this request
|
||||
*/
|
||||
void setSession(Object session);
|
||||
|
||||
/**
|
||||
* Set the offered WebSocket Sub-Protocol list.
|
||||
*
|
||||
* @param protocols the offered sub-protocol list
|
||||
*/
|
||||
void setSubProtocols(List<String> protocols);
|
||||
|
||||
/**
|
||||
* Set the offered WebSocket Sub-Protocol list.
|
||||
*
|
||||
* @param protocols the offered sub-protocol list
|
||||
*/
|
||||
void setSubProtocols(String... protocols);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,119 +19,92 @@
|
|||
package org.eclipse.jetty.websocket.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
|
||||
public class UpgradeResponse
|
||||
/**
|
||||
* The HTTP Upgrade to WebSocket Response
|
||||
*/
|
||||
public interface UpgradeResponse
|
||||
{
|
||||
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
|
||||
private int statusCode;
|
||||
private String statusReason;
|
||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private List<ExtensionConfig> extensions = new ArrayList<>();
|
||||
private boolean success = false;
|
||||
|
||||
public void addHeader(String name, String value)
|
||||
{
|
||||
String key = name;
|
||||
List<String> values = headers.get(key);
|
||||
if (values == null)
|
||||
{
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
values.add(value);
|
||||
headers.put(key,values);
|
||||
}
|
||||
/**
|
||||
* Add a header value to the response.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
void addHeader(String name, String value);
|
||||
|
||||
/**
|
||||
* Get the accepted WebSocket protocol.
|
||||
*
|
||||
* @return the accepted WebSocket protocol.
|
||||
*/
|
||||
public String getAcceptedSubProtocol()
|
||||
{
|
||||
return getHeader(SEC_WEBSOCKET_PROTOCOL);
|
||||
}
|
||||
String getAcceptedSubProtocol();
|
||||
|
||||
/**
|
||||
* Get the list of extensions that should be used for the websocket.
|
||||
*
|
||||
* @return the list of negotiated extensions to use.
|
||||
*/
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
List<ExtensionConfig> getExtensions();
|
||||
|
||||
public String getHeader(String name)
|
||||
{
|
||||
List<String> values = getHeaders(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return values.get(0);
|
||||
}
|
||||
// join it with commas
|
||||
boolean needsDelim = false;
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (String value : values)
|
||||
{
|
||||
if (needsDelim)
|
||||
{
|
||||
ret.append(", ");
|
||||
}
|
||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||
needsDelim = true;
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
/**
|
||||
* Get a header value
|
||||
*
|
||||
* @param name the header name
|
||||
* @return the value (null if header doesn't exist)
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
public Set<String> getHeaderNames()
|
||||
{
|
||||
return headers.keySet();
|
||||
}
|
||||
/**
|
||||
* Get the header names
|
||||
*
|
||||
* @return the set of header names
|
||||
*/
|
||||
Set<String> getHeaderNames();
|
||||
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
/**
|
||||
* Get the headers map
|
||||
*
|
||||
* @return the map of headers
|
||||
*/
|
||||
Map<String, List<String>> getHeaders();
|
||||
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return headers.get(name);
|
||||
}
|
||||
/**
|
||||
* Get the multi-value header value
|
||||
*
|
||||
* @param name the header name
|
||||
* @return the list of values (null if header doesn't exist)
|
||||
*/
|
||||
List<String> getHeaders(String name);
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
/**
|
||||
* Get the HTTP Response Status Code
|
||||
*
|
||||
* @return the status code
|
||||
*/
|
||||
int getStatusCode();
|
||||
|
||||
public String getStatusReason()
|
||||
{
|
||||
return statusReason;
|
||||
}
|
||||
/**
|
||||
* Get the HTTP Response Status Reason
|
||||
*
|
||||
* @return the HTTP Response status reason
|
||||
*/
|
||||
String getStatusReason();
|
||||
|
||||
public boolean isSuccess()
|
||||
{
|
||||
return success;
|
||||
}
|
||||
/**
|
||||
* Test if upgrade response is successful.
|
||||
* <p>
|
||||
* Merely notes if the response was sent as a WebSocket Upgrade,
|
||||
* or was failed (resulting in no upgrade handshake)
|
||||
*
|
||||
* @return true if upgrade response was generated, false if no upgrade response was generated
|
||||
*/
|
||||
boolean isSuccess();
|
||||
|
||||
/**
|
||||
* Issue a forbidden upgrade response.
|
||||
|
@ -142,67 +115,69 @@ public class UpgradeResponse
|
|||
* Use this when the origin or authentication is invalid.
|
||||
*
|
||||
* @param message
|
||||
* the short 1 line detail message about the forbidden response
|
||||
* the short 1 line detail message about the forbidden response
|
||||
* @throws IOException
|
||||
* if unable to send the forbidden
|
||||
* if unable to send the forbidden
|
||||
*/
|
||||
public void sendForbidden(String message) throws IOException
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
void sendForbidden(String message) throws IOException;
|
||||
|
||||
/**
|
||||
* Set the accepted WebSocket Protocol.
|
||||
*
|
||||
* @param protocol
|
||||
* the protocol to list as accepted
|
||||
* the protocol to list as accepted
|
||||
*/
|
||||
public void setAcceptedSubProtocol(String protocol)
|
||||
{
|
||||
setHeader(SEC_WEBSOCKET_PROTOCOL,protocol);
|
||||
}
|
||||
void setAcceptedSubProtocol(String protocol);
|
||||
|
||||
/**
|
||||
* Set the list of extensions that are approved for use with this websocket.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
|
||||
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the complete list of extensions that are
|
||||
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove
|
||||
* entries you don't want to use</li>
|
||||
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the
|
||||
* complete list of extensions that are
|
||||
* available in this WebSocket server implementation.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param extensions
|
||||
* the list of extensions to use.
|
||||
* the list of extensions to use.
|
||||
*/
|
||||
public void setExtensions(List<ExtensionConfig> extensions)
|
||||
{
|
||||
this.extensions.clear();
|
||||
if (extensions != null)
|
||||
{
|
||||
this.extensions.addAll(extensions);
|
||||
}
|
||||
}
|
||||
void setExtensions(List<ExtensionConfig> extensions);
|
||||
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
headers.put(name,values);
|
||||
}
|
||||
/**
|
||||
* Set a header
|
||||
* <p>
|
||||
* Overrides previous value of header (if set)
|
||||
*
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
void setHeader(String name, String value);
|
||||
|
||||
public void setStatusCode(int statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
/**
|
||||
* Set the HTTP Response status code
|
||||
*
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
void setStatusCode(int statusCode);
|
||||
|
||||
public void setStatusReason(String statusReason)
|
||||
{
|
||||
this.statusReason = statusReason;
|
||||
}
|
||||
/**
|
||||
* Set the HTTP Response status reason phrase
|
||||
* <p>
|
||||
* Note, not all implementation of UpgradeResponse can support this feature
|
||||
*
|
||||
* @param statusReason the status reason phrase
|
||||
*/
|
||||
void setStatusReason(String statusReason);
|
||||
|
||||
public void setSuccess(boolean success)
|
||||
{
|
||||
this.success = success;
|
||||
}
|
||||
/**
|
||||
* Set the success of the upgrade response.
|
||||
* <p>
|
||||
*
|
||||
* @param success true to indicate a response to the upgrade handshake was sent, false to indicate no upgrade
|
||||
* response was sent
|
||||
*/
|
||||
void setSuccess(boolean success);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.api;
|
||||
|
||||
public final class WebSocketConstants
|
||||
{
|
||||
public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
|
||||
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
|
||||
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
|
||||
public static final int SPEC_VERSION = 13;
|
||||
}
|
|
@ -25,6 +25,11 @@
|
|||
<artifactId>jetty-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-common</artifactId>
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -30,22 +30,21 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.LazyList;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter;
|
||||
|
||||
/**
|
||||
* Allowing a generate from a UpgradeRequest
|
||||
*/
|
||||
public class ClientUpgradeRequest extends UpgradeRequest
|
||||
public class ClientUpgradeRequest extends UpgradeRequestAdapter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
|
||||
private static final Set<String> FORBIDDEN_HEADERS;
|
||||
|
||||
static
|
||||
|
@ -68,6 +67,7 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
|||
}
|
||||
|
||||
private final String key;
|
||||
private Object localEndpoint;
|
||||
|
||||
public ClientUpgradeRequest()
|
||||
{
|
||||
|
@ -80,125 +80,46 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
|||
super(requestURI);
|
||||
this.key = genRandomKey();
|
||||
}
|
||||
|
||||
public String generate()
|
||||
|
||||
public ClientUpgradeRequest(WebSocketUpgradeRequest wsRequest)
|
||||
{
|
||||
URI uri = getRequestURI();
|
||||
|
||||
StringBuilder request = new StringBuilder(512);
|
||||
request.append("GET ");
|
||||
if (StringUtil.isBlank(uri.getPath()))
|
||||
this(wsRequest.getURI());
|
||||
// cookies
|
||||
this.setCookies(wsRequest.getCookies());
|
||||
// headers
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
HttpFields fields = wsRequest.getHeaders();
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
request.append("/");
|
||||
}
|
||||
else
|
||||
{
|
||||
request.append(uri.getPath());
|
||||
}
|
||||
if (StringUtil.isNotBlank(uri.getRawQuery()))
|
||||
{
|
||||
request.append("?").append(uri.getRawQuery());
|
||||
}
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
|
||||
request.append("Host: ").append(uri.getHost());
|
||||
if (uri.getPort() > 0)
|
||||
{
|
||||
request.append(':').append(uri.getPort());
|
||||
}
|
||||
request.append("\r\n");
|
||||
|
||||
// WebSocket specifics
|
||||
request.append("Upgrade: websocket\r\n");
|
||||
request.append("Connection: Upgrade\r\n");
|
||||
request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
|
||||
request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
|
||||
|
||||
// (Per the hybi list): Add no-cache headers to avoid compatibility issue.
|
||||
// There are some proxies that rewrite "Connection: upgrade"
|
||||
// to "Connection: close" in the response if a request doesn't contain
|
||||
// these headers.
|
||||
request.append("Pragma: no-cache\r\n");
|
||||
request.append("Cache-Control: no-cache\r\n");
|
||||
|
||||
// Extensions
|
||||
if (!getExtensions().isEmpty())
|
||||
{
|
||||
request.append("Sec-WebSocket-Extensions: ");
|
||||
boolean needDelim = false;
|
||||
for (ExtensionConfig ext : getExtensions())
|
||||
String key = field.getName();
|
||||
List<String> values = headers.get(key);
|
||||
if (values == null)
|
||||
{
|
||||
if (needDelim)
|
||||
{
|
||||
request.append(", ");
|
||||
}
|
||||
request.append(ext.getParameterizedName());
|
||||
needDelim = true;
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Sub Protocols
|
||||
if (!getSubProtocols().isEmpty())
|
||||
{
|
||||
request.append("Sec-WebSocket-Protocol: ");
|
||||
boolean needDelim = false;
|
||||
for (String protocol : getSubProtocols())
|
||||
values.addAll(Arrays.asList(field.getValues()));
|
||||
headers.put(key,values);
|
||||
// sub protocols
|
||||
if(key.equalsIgnoreCase("Sec-WebSocket-Protocol"))
|
||||
{
|
||||
if (needDelim)
|
||||
for(String subProtocol: field.getValue().split(","))
|
||||
{
|
||||
request.append(", ");
|
||||
setSubProtocols(subProtocol);
|
||||
}
|
||||
request.append(protocol);
|
||||
needDelim = true;
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Cookies
|
||||
List<HttpCookie> cookies = getCookies();
|
||||
if ((cookies != null) && (cookies.size() > 0))
|
||||
{
|
||||
request.append("Cookie: ");
|
||||
boolean needDelim = false;
|
||||
for (HttpCookie cookie : cookies)
|
||||
// extensions
|
||||
if(key.equalsIgnoreCase("Sec-WebSocket-Extensions"))
|
||||
{
|
||||
if (needDelim)
|
||||
for(ExtensionConfig ext: ExtensionConfig.parseList(field.getValues()))
|
||||
{
|
||||
request.append("; ");
|
||||
addExtensions(ext);
|
||||
}
|
||||
|
||||
request.append(cookie.getName()).append("=");
|
||||
if (cookie.getVersion() == 1)
|
||||
{
|
||||
// must be enclosed with quotes
|
||||
request.append('"').append(cookie.getValue()).append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
request.append(cookie.getValue());
|
||||
}
|
||||
needDelim = true;
|
||||
}
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// Other headers
|
||||
for (String key : getHeaders().keySet())
|
||||
{
|
||||
if (FORBIDDEN_HEADERS.contains(key))
|
||||
{
|
||||
LOG.debug("Skipping forbidden header - {}",key);
|
||||
continue; // skip
|
||||
}
|
||||
request.append(key).append(": ");
|
||||
request.append(getHeader(key));
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
// request header end
|
||||
request.append("\r\n");
|
||||
return request.toString();
|
||||
super.setHeaders(headers);
|
||||
// sessions
|
||||
setHttpVersion(wsRequest.getVersion().toString());
|
||||
setMethod(wsRequest.getMethod());
|
||||
}
|
||||
|
||||
private final String genRandomKey()
|
||||
|
@ -213,26 +134,14 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
|||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cookieStore the cookie store to use
|
||||
* @deprecated use either {@link WebSocketClient#setCookieStore(CookieStore)} or {@link HttpClient#setCookieStore(CookieStore)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCookiesFrom(CookieStore cookieStore)
|
||||
{
|
||||
if (cookieStore == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<HttpCookie> existing = getCookies();
|
||||
List<HttpCookie> extra = cookieStore.get(getRequestURI());
|
||||
|
||||
List<HttpCookie> cookies = new ArrayList<>();
|
||||
if (LazyList.hasEntry(existing))
|
||||
{
|
||||
cookies.addAll(existing);
|
||||
}
|
||||
if (LazyList.hasEntry(extra))
|
||||
{
|
||||
cookies.addAll(extra);
|
||||
}
|
||||
setCookies(cookies);
|
||||
throw new UnsupportedOperationException("Request specific CookieStore no longer supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -269,4 +178,14 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
|||
super.setParameterMap(pmap);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalEndpoint(Object websocket)
|
||||
{
|
||||
this.localEndpoint = websocket;
|
||||
}
|
||||
|
||||
public Object getLocalEndpoint()
|
||||
{
|
||||
return localEndpoint;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,27 +19,46 @@
|
|||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
|
||||
import org.eclipse.jetty.client.HttpResponse;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.common.UpgradeResponseAdapter;
|
||||
|
||||
public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
|
||||
public class ClientUpgradeResponse extends UpgradeResponseAdapter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientUpgradeResponse.class);
|
||||
private ByteBuffer remainingBuffer;
|
||||
private List<ExtensionConfig> extensions;
|
||||
|
||||
public ClientUpgradeResponse()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public ByteBuffer getRemainingBuffer()
|
||||
|
||||
public ClientUpgradeResponse(HttpResponse response)
|
||||
{
|
||||
return remainingBuffer;
|
||||
super();
|
||||
setStatusCode(response.getStatus());
|
||||
setStatusReason(response.getReason());
|
||||
|
||||
HttpFields fields = response.getHeaders();
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
addHeader(field.getName(),field.getValue());
|
||||
}
|
||||
|
||||
HttpField extensionsField = fields.getField(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||
if (extensionsField != null)
|
||||
this.extensions = ExtensionConfig.parseList(extensionsField.getValues());
|
||||
setAcceptedSubProtocol(fields.get(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return this.extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,14 +66,4 @@ public class ClientUpgradeResponse extends UpgradeResponse implements HttpRespon
|
|||
{
|
||||
throw new UnsupportedOperationException("Not supported on client implementation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemainingBuffer(ByteBuffer remainingBuffer)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Saving remaining header: {}",BufferUtil.toDetailString(remainingBuffer));
|
||||
}
|
||||
this.remainingBuffer = remainingBuffer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
|
||||
/**
|
||||
* WebSocket endpoint that does nothing.
|
||||
*/
|
||||
public class NoOpEndpoint extends WebSocketAdapter
|
||||
{
|
||||
/* does nothing */
|
||||
}
|
|
@ -28,26 +28,23 @@ import java.util.Locale;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
import org.eclipse.jetty.util.HttpCookieStore;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.ShutdownThread;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||
import org.eclipse.jetty.websocket.client.io.ConnectPromise;
|
||||
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
||||
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
||||
|
@ -55,7 +52,6 @@ import org.eclipse.jetty.websocket.client.masks.RandomMasker;
|
|||
import org.eclipse.jetty.websocket.common.SessionFactory;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
|
||||
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
|
||||
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
|
||||
|
@ -67,95 +63,262 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketClient.class);
|
||||
|
||||
private final WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
|
||||
private final SslContextFactory sslContextFactory;
|
||||
private final WebSocketExtensionFactory extensionRegistry;
|
||||
private boolean daemon = false;
|
||||
private EventDriverFactory eventDriverFactory;
|
||||
private SessionFactory sessionFactory;
|
||||
private ByteBufferPool bufferPool;
|
||||
private Executor executor;
|
||||
private DecoratedObjectFactory objectFactory;
|
||||
private Scheduler scheduler;
|
||||
private CookieStore cookieStore;
|
||||
private ConnectionManager connectionManager;
|
||||
private Masker masker;
|
||||
private SocketAddress bindAddress;
|
||||
private long connectTimeout = SelectorManager.DEFAULT_CONNECT_TIMEOUT;
|
||||
private boolean dispatchIO = true;
|
||||
// From HttpClient
|
||||
private final HttpClient httpClient;
|
||||
|
||||
// Other
|
||||
private final WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
|
||||
private final WebSocketExtensionFactory extensionRegistry;
|
||||
private final EventDriverFactory eventDriverFactory;
|
||||
private final SessionFactory sessionFactory;
|
||||
private final DecoratedObjectFactory objectFactory;
|
||||
private Masker masker;
|
||||
|
||||
private final int id = ThreadLocalRandom.current().nextInt();
|
||||
|
||||
/**
|
||||
* Instantiate a WebSocketClient with defaults
|
||||
*/
|
||||
public WebSocketClient()
|
||||
{
|
||||
this((SslContextFactory)null,null);
|
||||
// Create synthetic HttpClient
|
||||
this(new HttpClient());
|
||||
addBean(this.httpClient);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate a WebSocketClient using HttpClient for defaults
|
||||
*
|
||||
* @param httpClient
|
||||
* the HttpClient to base internal defaults off of
|
||||
*/
|
||||
public WebSocketClient(HttpClient httpClient)
|
||||
{
|
||||
this(httpClient,new DecoratedObjectFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a WebSocketClient using HttpClient for defaults
|
||||
*
|
||||
* @param httpClient
|
||||
* the HttpClient to base internal defaults off of
|
||||
* @param objectFactory
|
||||
* the DecoratedObjectFactory for all client instantiated classes
|
||||
*/
|
||||
public WebSocketClient(HttpClient httpClient, DecoratedObjectFactory objectFactory)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.objectFactory = objectFactory;
|
||||
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||
this.masker = new RandomMasker();
|
||||
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||
this.sessionFactory = new WebSocketSessionFactory(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new WebSocketClient
|
||||
*
|
||||
* @param executor
|
||||
* the executor to use
|
||||
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public WebSocketClient(Executor executor)
|
||||
{
|
||||
this(null,executor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new WebSocketClient
|
||||
*
|
||||
* @param bufferPool
|
||||
* byte buffer pool to use
|
||||
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public WebSocketClient(ByteBufferPool bufferPool)
|
||||
{
|
||||
this(null,null,bufferPool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new WebSocketClient
|
||||
*
|
||||
* @param sslContextFactory
|
||||
* ssl context factory to use
|
||||
*/
|
||||
public WebSocketClient(SslContextFactory sslContextFactory)
|
||||
{
|
||||
this(sslContextFactory,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new WebSocketClient
|
||||
*
|
||||
* @param sslContextFactory
|
||||
* ssl context factory to use
|
||||
* @param executor
|
||||
* the executor to use
|
||||
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
|
||||
{
|
||||
this(sslContextFactory,executor,new MappedByteBufferPool());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create WebSocketClient other Container Scope, to allow sharing of
|
||||
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||
*
|
||||
* @param scope
|
||||
* the Container Scope
|
||||
*/
|
||||
public WebSocketClient(WebSocketContainerScope scope)
|
||||
{
|
||||
this(scope.getSslContextFactory(), scope.getExecutor(), scope.getBufferPool(), scope.getObjectFactory());
|
||||
this(scope.getSslContextFactory(),scope.getExecutor(),scope.getBufferPool(),scope.getObjectFactory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create WebSocketClient other Container Scope, to allow sharing of
|
||||
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||
*
|
||||
* @param scope
|
||||
* the Container Scope
|
||||
* @param sslContextFactory
|
||||
* SSL ContextFactory to use in preference to one from
|
||||
* {@link WebSocketContainerScope#getSslContextFactory()}
|
||||
*/
|
||||
public WebSocketClient(WebSocketContainerScope scope, SslContextFactory sslContextFactory)
|
||||
{
|
||||
this(sslContextFactory, scope.getExecutor(), scope.getBufferPool(), scope.getObjectFactory());
|
||||
this(sslContextFactory,scope.getExecutor(),scope.getBufferPool(),scope.getObjectFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create WebSocketClient using sharing instances of SSLContextFactory
|
||||
* Executor, and ByteBufferPool
|
||||
*
|
||||
* @param sslContextFactory
|
||||
* shared SSL ContextFactory
|
||||
* @param executor
|
||||
* shared Executor
|
||||
* @param bufferPool
|
||||
* shared ByteBufferPool
|
||||
*/
|
||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool)
|
||||
{
|
||||
this(sslContextFactory, executor, bufferPool, new DecoratedObjectFactory());
|
||||
this(sslContextFactory,executor,bufferPool,new DecoratedObjectFactory());
|
||||
}
|
||||
|
||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
|
||||
/**
|
||||
* Create WebSocketClient using sharing instances of SSLContextFactory
|
||||
* Executor, and ByteBufferPool
|
||||
*
|
||||
* @param sslContextFactory
|
||||
* shared SSL ContextFactory
|
||||
* @param executor
|
||||
* shared Executor
|
||||
* @param bufferPool
|
||||
* shared ByteBufferPool
|
||||
* @param objectFactory
|
||||
* shared DecoratedObjectFactory
|
||||
*/
|
||||
private WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
|
||||
{
|
||||
this.httpClient = new HttpClient(sslContextFactory);
|
||||
this.httpClient.setExecutor(executor);
|
||||
this.httpClient.setByteBufferPool(bufferPool);
|
||||
addBean(this.httpClient);
|
||||
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
if(sslContextFactory!=null)
|
||||
addBean(sslContextFactory);
|
||||
setExecutor(executor);
|
||||
setBufferPool(bufferPool);
|
||||
|
||||
this.objectFactory = objectFactory;
|
||||
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||
|
||||
this.masker = new RandomMasker();
|
||||
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||
}
|
||||
if (objectFactory == null)
|
||||
this.objectFactory = new DecoratedObjectFactory();
|
||||
else
|
||||
this.objectFactory = objectFactory;
|
||||
|
||||
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||
|
||||
this.masker = new RandomMasker();
|
||||
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||
this.sessionFactory = new WebSocketSessionFactory(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create WebSocketClient based on pre-existing Container Scope, to allow sharing of
|
||||
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||
*
|
||||
* @param scope
|
||||
* the Container Scope
|
||||
* @param eventDriverFactory
|
||||
* the EventDriver Factory to use
|
||||
* @param sessionFactory
|
||||
* the SessionFactory to use
|
||||
*/
|
||||
public WebSocketClient(WebSocketContainerScope scope, EventDriverFactory eventDriverFactory, SessionFactory sessionFactory)
|
||||
{
|
||||
SslContextFactory sslContextFactory = scope.getSslContextFactory();
|
||||
if(sslContextFactory == null)
|
||||
{
|
||||
sslContextFactory = new SslContextFactory();
|
||||
}
|
||||
this.httpClient = new HttpClient(sslContextFactory);
|
||||
this.httpClient.setExecutor(scope.getExecutor());
|
||||
addBean(this.httpClient);
|
||||
|
||||
this.objectFactory = new DecoratedObjectFactory();
|
||||
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||
|
||||
this.masker = new RandomMasker();
|
||||
this.eventDriverFactory = eventDriverFactory;
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
public Future<Session> connect(Object websocket, URI toUri) throws IOException
|
||||
{
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest(toUri);
|
||||
request.setRequestURI(toUri);
|
||||
request.setCookiesFrom(this.cookieStore);
|
||||
request.setLocalEndpoint(websocket);
|
||||
|
||||
return connect(websocket,toUri,request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to remote websocket endpoint
|
||||
*
|
||||
* @param websocket
|
||||
* the websocket object
|
||||
* @param toUri
|
||||
* the websocket uri to connect to
|
||||
* @param request
|
||||
* the upgrade request information
|
||||
* @return the future for the session, available on success of connect
|
||||
* @throws IOException
|
||||
* if unable to connect
|
||||
*/
|
||||
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
|
||||
{
|
||||
return connect(websocket,toUri,request,null);
|
||||
return connect(websocket,toUri,request,(UpgradeListener)null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect to remote websocket endpoint
|
||||
*
|
||||
* @param websocket
|
||||
* the websocket object
|
||||
* @param toUri
|
||||
* the websocket uri to connect to
|
||||
* @param request
|
||||
* the upgrade request information
|
||||
* @param upgradeListener
|
||||
* the upgrade listener
|
||||
* @return the future for the session, available on success of connect
|
||||
* @throws IOException
|
||||
* if unable to connect
|
||||
*/
|
||||
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException
|
||||
{
|
||||
/* Note: UpgradeListener is used by javax.websocket.ClientEndpointConfig.Configurator
|
||||
* See: org.eclipse.jetty.websocket.jsr356.JsrUpgradeListener
|
||||
*/
|
||||
if (!isStarted())
|
||||
{
|
||||
throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started");
|
||||
|
@ -179,7 +342,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
}
|
||||
|
||||
request.setRequestURI(toUri);
|
||||
request.setCookiesFrom(this.cookieStore);
|
||||
request.setLocalEndpoint(websocket);
|
||||
|
||||
// Validate Requested Extensions
|
||||
for (ExtensionConfig reqExt : request.getExtensions())
|
||||
|
@ -193,84 +356,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("connect websocket {} to {}",websocket,toUri);
|
||||
|
||||
// Grab Connection Manager
|
||||
initializeClient();
|
||||
ConnectionManager manager = getConnectionManager();
|
||||
|
||||
// Setup Driver for user provided websocket
|
||||
EventDriver driver = null;
|
||||
if (websocket instanceof EventDriver)
|
||||
{
|
||||
// Use the EventDriver as-is
|
||||
driver = (EventDriver)websocket;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wrap websocket with appropriate EventDriver
|
||||
driver = eventDriverFactory.wrap(websocket);
|
||||
}
|
||||
|
||||
if (driver == null)
|
||||
{
|
||||
throw new IllegalStateException("Unable to identify as websocket object: " + websocket.getClass().getName());
|
||||
}
|
||||
|
||||
// Create the appropriate (physical vs virtual) connection task
|
||||
ConnectPromise promise = manager.connect(this,driver,request);
|
||||
|
||||
if (upgradeListener != null)
|
||||
{
|
||||
promise.setUpgradeListener(upgradeListener);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Connect Promise: {}",promise);
|
||||
|
||||
// Execute the connection on the executor thread
|
||||
executor.execute(promise);
|
||||
|
||||
// Return the future
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Starting {}",this);
|
||||
|
||||
String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
|
||||
|
||||
if (bufferPool == null)
|
||||
{
|
||||
setBufferPool(new MappedByteBufferPool());
|
||||
}
|
||||
|
||||
if (scheduler == null)
|
||||
{
|
||||
scheduler = new ScheduledExecutorScheduler(name + "-scheduler",daemon);
|
||||
addBean(scheduler);
|
||||
}
|
||||
|
||||
if (cookieStore == null)
|
||||
{
|
||||
setCookieStore(new HttpCookieStore.Empty());
|
||||
}
|
||||
|
||||
if(this.sessionFactory == null)
|
||||
{
|
||||
setSessionFactory(new WebSocketSessionFactory(this));
|
||||
}
|
||||
init();
|
||||
|
||||
WebSocketUpgradeRequest wsReq = new WebSocketUpgradeRequest(this,httpClient,request);
|
||||
|
||||
if(this.objectFactory == null)
|
||||
{
|
||||
this.objectFactory = new DecoratedObjectFactory();
|
||||
}
|
||||
|
||||
super.doStart();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Started {}",this);
|
||||
wsReq.setUpgradeListener(upgradeListener);
|
||||
return wsReq.sendAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -287,19 +378,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
super.doStop();
|
||||
|
||||
if (cookieStore != null)
|
||||
{
|
||||
cookieStore.removeAll();
|
||||
cookieStore = null;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stopped {}",this);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean isDispatchIO()
|
||||
{
|
||||
return dispatchIO;
|
||||
return httpClient.isDispatchIO();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -314,29 +400,30 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
public SocketAddress getBindAddress()
|
||||
{
|
||||
return bindAddress;
|
||||
return httpClient.getBindAddress();
|
||||
}
|
||||
|
||||
public ByteBufferPool getBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
return httpClient.getByteBufferPool();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ConnectionManager getConnectionManager()
|
||||
{
|
||||
return connectionManager;
|
||||
throw new UnsupportedOperationException("ConnectionManager is no longer supported");
|
||||
}
|
||||
|
||||
public long getConnectTimeout()
|
||||
{
|
||||
return connectTimeout;
|
||||
return httpClient.getConnectTimeout();
|
||||
}
|
||||
|
||||
public CookieStore getCookieStore()
|
||||
{
|
||||
return cookieStore;
|
||||
return httpClient.getCookieStore();
|
||||
}
|
||||
|
||||
|
||||
public EventDriverFactory getEventDriverFactory()
|
||||
{
|
||||
return eventDriverFactory;
|
||||
|
@ -344,7 +431,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
public Executor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
return httpClient.getExecutor();
|
||||
}
|
||||
|
||||
public ExtensionFactory getExtensionFactory()
|
||||
|
@ -425,9 +512,9 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
return httpClient.getScheduler();
|
||||
}
|
||||
|
||||
|
||||
public SessionFactory getSessionFactory()
|
||||
{
|
||||
return sessionFactory;
|
||||
|
@ -439,45 +526,27 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
*/
|
||||
public SslContextFactory getSslContextFactory()
|
||||
{
|
||||
return sslContextFactory;
|
||||
return httpClient.getSslContextFactory();
|
||||
}
|
||||
|
||||
private synchronized void initializeClient() throws IOException
|
||||
private synchronized void init() throws IOException
|
||||
{
|
||||
if (!ShutdownThread.isRegistered(this))
|
||||
{
|
||||
ShutdownThread.register(this);
|
||||
}
|
||||
|
||||
if (executor == null)
|
||||
{
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
|
||||
threadPool.setName(name);
|
||||
threadPool.setDaemon(daemon);
|
||||
executor = threadPool;
|
||||
addManaged(threadPool);
|
||||
}
|
||||
else
|
||||
{
|
||||
addBean(executor,false);
|
||||
}
|
||||
|
||||
if (connectionManager == null)
|
||||
{
|
||||
connectionManager = newConnectionManager();
|
||||
addManaged(connectionManager);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for new ConnectionManager (used by other projects like cometd)
|
||||
* Factory method for new ConnectionManager
|
||||
*
|
||||
* @return the ConnectionManager instance to use
|
||||
* @deprecated use HttpClient instead
|
||||
*/
|
||||
@Deprecated
|
||||
protected ConnectionManager newConnectionManager()
|
||||
{
|
||||
return new ConnectionManager(this);
|
||||
throw new UnsupportedOperationException("ConnectionManager is no longer supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -494,16 +563,18 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Session Opened: {}",session);
|
||||
addManaged(session);
|
||||
LOG.debug("post-onSessionOpened() - {}", this);
|
||||
}
|
||||
|
||||
|
||||
public void setAsyncWriteTimeout(long ms)
|
||||
{
|
||||
this.policy.setAsyncWriteTimeout(ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bindAddress the address to bind to
|
||||
* @deprecated use {@link #setBindAddress(SocketAddress)} instead
|
||||
* @param bindAddress
|
||||
* the address to bind to
|
||||
* @deprecated (this is a bad bad bad typo) use {@link #setBindAddress(SocketAddress)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setBindAdddress(SocketAddress bindAddress)
|
||||
|
@ -513,13 +584,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
|
||||
public void setBindAddress(SocketAddress bindAddress)
|
||||
{
|
||||
this.bindAddress = bindAddress;
|
||||
this.httpClient.setBindAddress(bindAddress);
|
||||
}
|
||||
|
||||
public void setBufferPool(ByteBufferPool bufferPool)
|
||||
{
|
||||
updateBean(this.bufferPool,bufferPool);
|
||||
this.bufferPool = bufferPool;
|
||||
this.httpClient.setByteBufferPool(bufferPool);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -530,38 +600,28 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
*/
|
||||
public void setConnectTimeout(long ms)
|
||||
{
|
||||
if (ms < 0)
|
||||
{
|
||||
throw new IllegalStateException("Connect Timeout cannot be negative");
|
||||
}
|
||||
this.connectTimeout = ms;
|
||||
this.httpClient.setConnectTimeout(ms);
|
||||
}
|
||||
|
||||
public void setCookieStore(CookieStore cookieStore)
|
||||
{
|
||||
updateBean(this.cookieStore,cookieStore);
|
||||
this.cookieStore = cookieStore;
|
||||
this.httpClient.setCookieStore(cookieStore);
|
||||
}
|
||||
|
||||
|
||||
public void setDaemon(boolean daemon)
|
||||
{
|
||||
this.daemon = daemon;
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setDispatchIO(boolean dispatchIO)
|
||||
{
|
||||
this.dispatchIO = dispatchIO;
|
||||
this.httpClient.setDispatchIO(dispatchIO);
|
||||
}
|
||||
|
||||
public void setEventDriverFactory(EventDriverFactory factory)
|
||||
{
|
||||
this.eventDriverFactory = factory;
|
||||
}
|
||||
|
||||
|
||||
public void setExecutor(Executor executor)
|
||||
{
|
||||
updateBean(this.executor,executor);
|
||||
this.executor = executor;
|
||||
this.httpClient.setExecutor(executor);
|
||||
}
|
||||
|
||||
public void setMasker(Masker masker)
|
||||
|
@ -573,7 +633,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
{
|
||||
this.policy.setMaxBinaryMessageBufferSize(max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the max idle timeout for new connections.
|
||||
* <p>
|
||||
|
@ -585,6 +645,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
public void setMaxIdleTimeout(long ms)
|
||||
{
|
||||
this.policy.setIdleTimeout(ms);
|
||||
this.httpClient.setIdleTimeout(ms);
|
||||
}
|
||||
|
||||
public void setMaxTextMessageBufferSize(int max)
|
||||
|
@ -592,16 +653,27 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
this.policy.setMaxTextMessageBufferSize(max);
|
||||
}
|
||||
|
||||
public void setSessionFactory(SessionFactory sessionFactory)
|
||||
{
|
||||
updateBean(this.sessionFactory,sessionFactory);
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
dumpThis(out);
|
||||
dump(out, indent, getOpenSessions());
|
||||
dump(out,indent,getOpenSessions());
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient()
|
||||
{
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder("WebSocketClient@");
|
||||
sb.append(Integer.toHexString(id));
|
||||
sb.append("[httpClient=").append(httpClient);
|
||||
sb.append(",openSessions.size=");
|
||||
sb.append(getOpenSessions().size());
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,634 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpConversation;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.HttpResponse;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Response.CompleteListener;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionUpgrader;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeException;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
||||
import org.eclipse.jetty.websocket.client.io.WebSocketClientConnection;
|
||||
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||
import org.eclipse.jetty.websocket.common.SessionFactory;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
|
||||
|
||||
public class WebSocketUpgradeRequest extends HttpRequest implements CompleteListener, HttpConnectionUpgrader
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketUpgradeRequest.class);
|
||||
|
||||
private class ClientUpgradeRequestFacade implements UpgradeRequest
|
||||
{
|
||||
private List<ExtensionConfig> extensions;
|
||||
private List<String> subProtocols;
|
||||
private Object session;
|
||||
|
||||
public ClientUpgradeRequestFacade()
|
||||
{
|
||||
this.extensions = new ArrayList<>();
|
||||
this.subProtocols = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void init(ClientUpgradeRequest request)
|
||||
{
|
||||
this.extensions = new ArrayList<>(request.getExtensions());
|
||||
this.subProtocols = new ArrayList<>(request.getSubProtocols());
|
||||
|
||||
// Copy values from ClientUpgradeRequest into place
|
||||
if (StringUtil.isNotBlank(request.getOrigin()))
|
||||
header(HttpHeader.ORIGIN,request.getOrigin());
|
||||
for (HttpCookie cookie : request.getCookies())
|
||||
{
|
||||
cookie(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
return subProtocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(ExtensionConfig... configs)
|
||||
{
|
||||
for (ExtensionConfig config : configs)
|
||||
{
|
||||
this.extensions.add(config);
|
||||
}
|
||||
updateExtensionHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(String... configs)
|
||||
{
|
||||
this.extensions.addAll(ExtensionConfig.parseList(configs));
|
||||
updateExtensionHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaders()
|
||||
{
|
||||
throw new UnsupportedOperationException("Clearing all headers breaks WebSocket upgrade");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
return getHttpFields().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeaderInt(String name)
|
||||
{
|
||||
String value = getHttpFields().get(name);
|
||||
if(value == null) {
|
||||
return -1;
|
||||
}
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return getHttpFields().getValuesList(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpVersion()
|
||||
{
|
||||
return getVersion().asString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOrigin()
|
||||
{
|
||||
return getHttpFields().get(HttpHeader.ORIGIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getParameterMap()
|
||||
{
|
||||
Map<String,List<String>> paramMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
String query = getQueryString();
|
||||
MultiMap<String> multimap = new MultiMap<>();
|
||||
UrlEncoded.decodeTo(query,multimap,StandardCharsets.UTF_8);
|
||||
|
||||
paramMap.putAll(multimap);
|
||||
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocolVersion()
|
||||
{
|
||||
String ver = getHttpFields().get(HttpHeader.SEC_WEBSOCKET_VERSION);
|
||||
if (ver == null)
|
||||
{
|
||||
return Integer.toString(WebSocketConstants.SPEC_VERSION);
|
||||
}
|
||||
return ver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
return getURI().getQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI()
|
||||
{
|
||||
return getURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSession()
|
||||
{
|
||||
return this.session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
// HttpClient doesn't use Principal concepts
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubProtocol(String test)
|
||||
{
|
||||
return getSubProtocols().contains(test);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOrigin(String test)
|
||||
{
|
||||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
// TODO: need to obtain information from actual request to know of SSL was used?
|
||||
return "wss".equalsIgnoreCase(getURI().getScheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
for(HttpCookie cookie: cookies)
|
||||
cookie(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
this.extensions = configs;
|
||||
updateExtensionHeader();
|
||||
}
|
||||
|
||||
private void updateExtensionHeader()
|
||||
{
|
||||
HttpFields headers = getHttpFields();
|
||||
headers.remove(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||
for (ExtensionConfig config : extensions)
|
||||
{
|
||||
headers.add(HttpHeader.SEC_WEBSOCKET_EXTENSIONS,config.getParameterizedName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, List<String> values)
|
||||
{
|
||||
getHttpFields().put(name,values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
getHttpFields().put(name,value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaders(Map<String, List<String>> headers)
|
||||
{
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
||||
{
|
||||
getHttpFields().put(entry.getKey(),entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHttpVersion(String httpVersion)
|
||||
{
|
||||
version(HttpVersion.fromString(httpVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMethod(String method)
|
||||
{
|
||||
method(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestURI(URI uri)
|
||||
{
|
||||
throw new UnsupportedOperationException("Cannot reset/change RequestURI");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSession(Object session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(List<String> protocols)
|
||||
{
|
||||
this.subProtocols = protocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(String... protocols)
|
||||
{
|
||||
this.subProtocols.clear();
|
||||
this.subProtocols.addAll(Arrays.asList(protocols));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
return WebSocketUpgradeRequest.this.getCookies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
Map<String, List<String>> headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
HttpFields fields = getHttpFields();
|
||||
for(String name: fields.getFieldNamesCollection())
|
||||
{
|
||||
headersMap.put(name,fields.getValuesList(name));
|
||||
}
|
||||
return headersMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
return WebSocketUpgradeRequest.this.getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return WebSocketUpgradeRequest.this.getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
private final WebSocketClient wsClient;
|
||||
private final EventDriver localEndpoint;
|
||||
private final CompletableFuture<Session> fut;
|
||||
/** WebSocket API UpgradeRequest Facade to HttpClient HttpRequest */
|
||||
private final ClientUpgradeRequestFacade apiRequestFacade;
|
||||
private UpgradeListener upgradeListener;
|
||||
|
||||
/**
|
||||
* Exists for internal use of HttpClient by WebSocketClient.
|
||||
* <p>
|
||||
* Maintained for Backward compatibility and also for JSR356 WebSocket ClientContainer use.
|
||||
*
|
||||
* @param httpClient the HttpClient that this request uses
|
||||
* @param request the ClientUpgradeRequest (backward compat) to base this request from
|
||||
*/
|
||||
protected WebSocketUpgradeRequest(WebSocketClient wsClient, HttpClient httpClient, ClientUpgradeRequest request)
|
||||
{
|
||||
this(wsClient, httpClient,request.getRequestURI(),request.getLocalEndpoint());
|
||||
apiRequestFacade.init(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiating a WebSocket Upgrade using HTTP/1.1
|
||||
*
|
||||
* @param httpClient the HttpClient that this request uses
|
||||
* @param localEndpoint the local endpoint (following Jetty WebSocket Client API rules) to use for incoming
|
||||
* WebSocket events
|
||||
* @param wsURI the WebSocket URI to connect to
|
||||
*/
|
||||
public WebSocketUpgradeRequest(WebSocketClient wsClient, HttpClient httpClient, URI wsURI, Object localEndpoint)
|
||||
{
|
||||
super(httpClient,new HttpConversation(),wsURI);
|
||||
|
||||
apiRequestFacade = new ClientUpgradeRequestFacade();
|
||||
|
||||
if (!wsURI.isAbsolute())
|
||||
{
|
||||
throw new IllegalArgumentException("WebSocket URI must be an absolute URI: " + wsURI);
|
||||
}
|
||||
|
||||
String scheme = wsURI.getScheme();
|
||||
if (scheme == null || !(scheme.equalsIgnoreCase("ws") || scheme.equalsIgnoreCase("wss")))
|
||||
{
|
||||
throw new IllegalArgumentException("WebSocket URI must use 'ws' or 'wss' scheme: " + wsURI);
|
||||
}
|
||||
|
||||
this.wsClient = wsClient;
|
||||
try
|
||||
{
|
||||
if (!this.wsClient.isRunning())
|
||||
{
|
||||
this.wsClient.start();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalStateException("Unable to start WebSocketClient", e);
|
||||
}
|
||||
this.localEndpoint = this.wsClient.getEventDriverFactory().wrap(localEndpoint);
|
||||
|
||||
this.fut = new CompletableFuture<Session>();
|
||||
}
|
||||
|
||||
private final String genRandomKey()
|
||||
{
|
||||
byte[] bytes = new byte[16];
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
return new String(B64Code.encode(bytes));
|
||||
}
|
||||
|
||||
private ExtensionFactory getExtensionFactory()
|
||||
{
|
||||
return this.wsClient.getExtensionFactory();
|
||||
}
|
||||
|
||||
private SessionFactory getSessionFactory()
|
||||
{
|
||||
return this.wsClient.getSessionFactory();
|
||||
}
|
||||
|
||||
private void initWebSocketHeaders()
|
||||
{
|
||||
method(HttpMethod.GET);
|
||||
version(HttpVersion.HTTP_1_1);
|
||||
|
||||
// The Upgrade Headers
|
||||
header(HttpHeader.UPGRADE,"websocket");
|
||||
header(HttpHeader.CONNECTION,"Upgrade");
|
||||
|
||||
// The WebSocket Headers
|
||||
header(HttpHeader.SEC_WEBSOCKET_KEY,genRandomKey());
|
||||
header(HttpHeader.SEC_WEBSOCKET_VERSION,"13");
|
||||
|
||||
// (Per the hybi list): Add no-cache headers to avoid compatibility issue.
|
||||
// There are some proxies that rewrite "Connection: upgrade"
|
||||
// to "Connection: close" in the response if a request doesn't contain
|
||||
// these headers.
|
||||
header(HttpHeader.PRAGMA,"no-cache");
|
||||
header(HttpHeader.CACHE_CONTROL,"no-cache");
|
||||
|
||||
// handle "Sec-WebSocket-Extensions"
|
||||
if (!apiRequestFacade.getExtensions().isEmpty())
|
||||
{
|
||||
for (ExtensionConfig ext : apiRequestFacade.getExtensions())
|
||||
{
|
||||
header(HttpHeader.SEC_WEBSOCKET_EXTENSIONS,ext.getParameterizedName());
|
||||
}
|
||||
}
|
||||
|
||||
// handle "Sec-WebSocket-Protocol"
|
||||
if (!apiRequestFacade.getSubProtocols().isEmpty())
|
||||
{
|
||||
for (String protocol : apiRequestFacade.getSubProtocols())
|
||||
{
|
||||
header(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL,protocol);
|
||||
}
|
||||
}
|
||||
|
||||
if (upgradeListener != null)
|
||||
{
|
||||
upgradeListener.onHandshakeRequest(apiRequestFacade);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("onComplete() - {}",result);
|
||||
}
|
||||
|
||||
URI requestURI = result.getRequest().getURI();
|
||||
Response response = result.getResponse();
|
||||
int responseStatusCode = response.getStatus();
|
||||
String responseLine = responseStatusCode + " " + response.getReason();
|
||||
|
||||
if (result.isFailed())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
if (result.getFailure() != null)
|
||||
LOG.debug("General Failure", result.getFailure());
|
||||
if (result.getRequestFailure() != null)
|
||||
LOG.debug("Request Failure", result.getRequestFailure());
|
||||
if (result.getResponseFailure() != null)
|
||||
LOG.debug("Response Failure", result.getResponseFailure());
|
||||
}
|
||||
|
||||
Throwable failure = result.getFailure();
|
||||
if ((failure instanceof java.net.ConnectException) || (failure instanceof UpgradeException))
|
||||
{
|
||||
// handle as-is
|
||||
handleException(failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
// wrap in UpgradeException
|
||||
handleException(new UpgradeException(requestURI,responseStatusCode,responseLine,failure));
|
||||
}
|
||||
}
|
||||
|
||||
if (responseStatusCode != HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||
{
|
||||
// Failed to upgrade (other reason)
|
||||
handleException(new UpgradeException(requestURI,responseStatusCode,responseLine));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleException(Throwable failure)
|
||||
{
|
||||
localEndpoint.incomingError(failure);
|
||||
fut.completeExceptionally(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
|
||||
{
|
||||
throw new RuntimeException("Working with raw ContentResponse is invalid for WebSocket");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(final CompleteListener listener)
|
||||
{
|
||||
initWebSocketHeaders();
|
||||
super.send(listener);
|
||||
}
|
||||
|
||||
public CompletableFuture<Session> sendAsync()
|
||||
{
|
||||
send(this);
|
||||
return fut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(HttpResponse response, HttpConnectionOverHTTP oldConn)
|
||||
{
|
||||
if (!this.getHeaders().get(HttpHeader.UPGRADE).equalsIgnoreCase("websocket"))
|
||||
{
|
||||
// Not my upgrade
|
||||
throw new HttpResponseException("Not WebSocket Upgrade",response);
|
||||
}
|
||||
|
||||
// Check the Accept hash
|
||||
String reqKey = this.getHeaders().get(HttpHeader.SEC_WEBSOCKET_KEY);
|
||||
String expectedHash = AcceptHash.hashKey(reqKey);
|
||||
String respHash = response.getHeaders().get(HttpHeader.SEC_WEBSOCKET_ACCEPT);
|
||||
|
||||
if (expectedHash.equalsIgnoreCase(respHash) == false)
|
||||
{
|
||||
throw new HttpResponseException("Invalid Sec-WebSocket-Accept hash",response);
|
||||
}
|
||||
|
||||
// We can upgrade
|
||||
EndPoint endp = oldConn.getEndPoint();
|
||||
|
||||
WebSocketClientConnection connection = new WebSocketClientConnection(endp,wsClient.getExecutor(),wsClient.getScheduler(),localEndpoint.getPolicy(),
|
||||
wsClient.getBufferPool());
|
||||
|
||||
URI requestURI = this.getURI();
|
||||
|
||||
WebSocketSession session = getSessionFactory().createSession(requestURI,localEndpoint,connection);
|
||||
session.setUpgradeRequest(new ClientUpgradeRequest(this));
|
||||
session.setUpgradeResponse(new ClientUpgradeResponse(response));
|
||||
connection.addListener(session);
|
||||
|
||||
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
|
||||
List<ExtensionConfig> extensions = new ArrayList<>();
|
||||
HttpField extField = response.getHeaders().getField(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||
if (extField != null)
|
||||
{
|
||||
String[] extValues = extField.getValues();
|
||||
if (extValues != null)
|
||||
{
|
||||
for (String extVal : extValues)
|
||||
{
|
||||
QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(tok.nextToken()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extensionStack.negotiate(extensions);
|
||||
|
||||
extensionStack.configure(connection.getParser());
|
||||
extensionStack.configure(connection.getGenerator());
|
||||
|
||||
// Setup Incoming Routing
|
||||
connection.setNextIncomingFrames(extensionStack);
|
||||
extensionStack.setNextIncoming(session);
|
||||
|
||||
// Setup Outgoing Routing
|
||||
session.setOutgoingHandler(extensionStack);
|
||||
extensionStack.setNextOutgoing(connection);
|
||||
|
||||
session.addManaged(extensionStack);
|
||||
session.setFuture(fut);
|
||||
wsClient.addManaged(session);
|
||||
|
||||
if (upgradeListener != null)
|
||||
{
|
||||
upgradeListener.onHandshakeResponse(new ClientUpgradeResponse(response));
|
||||
}
|
||||
|
||||
// Now swap out the connection
|
||||
endp.upgrade(connection);
|
||||
}
|
||||
|
||||
public void setUpgradeListener(UpgradeListener upgradeListener)
|
||||
{
|
||||
this.upgradeListener = upgradeListener;
|
||||
}
|
||||
|
||||
private HttpFields getHttpFields()
|
||||
{
|
||||
return super.getHeaders();
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client.io;
|
||||
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
|
||||
/**
|
||||
* Holder for the pending connect information.
|
||||
*/
|
||||
public abstract class ConnectPromise extends FuturePromise<Session> implements Runnable
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ConnectPromise.class);
|
||||
private final WebSocketClient client;
|
||||
private final EventDriver driver;
|
||||
private final ClientUpgradeRequest request;
|
||||
private final Masker masker;
|
||||
private UpgradeListener upgradeListener;
|
||||
private ClientUpgradeResponse response;
|
||||
private WebSocketSession session;
|
||||
|
||||
public ConnectPromise(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
||||
{
|
||||
this.client = client;
|
||||
this.driver = driver;
|
||||
this.request = request;
|
||||
this.masker = client.getMasker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable cause)
|
||||
{
|
||||
// Notify websocket of failure to connect
|
||||
driver.onError(cause);
|
||||
|
||||
// Notify promise/future of failure to connect
|
||||
super.failed(cause);
|
||||
}
|
||||
|
||||
public WebSocketClient getClient()
|
||||
{
|
||||
return client;
|
||||
}
|
||||
|
||||
public EventDriver getDriver()
|
||||
{
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
public Masker getMasker()
|
||||
{
|
||||
return masker;
|
||||
}
|
||||
|
||||
public ClientUpgradeRequest getRequest()
|
||||
{
|
||||
return this.request;
|
||||
}
|
||||
|
||||
public ClientUpgradeResponse getResponse()
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
public UpgradeListener getUpgradeListener()
|
||||
{
|
||||
return upgradeListener;
|
||||
}
|
||||
|
||||
public void setResponse(ClientUpgradeResponse response)
|
||||
{
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public void setUpgradeListener(UpgradeListener upgradeListener)
|
||||
{
|
||||
this.upgradeListener = upgradeListener;
|
||||
}
|
||||
|
||||
public void succeeded()
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("{}.succeeded()",this.getClass().getSimpleName());
|
||||
session.setUpgradeRequest(request);
|
||||
session.setUpgradeResponse(response);
|
||||
// session.open();
|
||||
super.succeeded(session);
|
||||
}
|
||||
|
||||
public void setSession(WebSocketSession session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
}
|
|
@ -18,87 +18,21 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.client.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
|
||||
/**
|
||||
* Internal Connection/Client Manager used to track active clients, their physical vs virtual connection information, and provide some means to create new
|
||||
* physical or virtual connections.
|
||||
* Deprecated ConnectionManager
|
||||
* @deprecated use {@link HttpClient} with WebSocketClient directly
|
||||
*/
|
||||
@Deprecated
|
||||
public class ConnectionManager extends ContainerLifeCycle
|
||||
{
|
||||
private class PhysicalConnect extends ConnectPromise
|
||||
{
|
||||
private SocketAddress bindAddress;
|
||||
|
||||
public PhysicalConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
||||
{
|
||||
super(client,driver,request);
|
||||
this.bindAddress = client.getBindAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
SocketChannel channel = null;
|
||||
try
|
||||
{
|
||||
channel = SocketChannel.open();
|
||||
if (bindAddress != null)
|
||||
{
|
||||
channel.bind(bindAddress);
|
||||
}
|
||||
|
||||
URI wsUri = getRequest().getRequestURI();
|
||||
|
||||
channel.socket().setTcpNoDelay(true); // disable nagle
|
||||
channel.configureBlocking(false); // async always
|
||||
|
||||
InetSocketAddress address = toSocketAddress(wsUri);
|
||||
|
||||
if (channel.connect(address))
|
||||
{
|
||||
getSelector().accept(channel, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
getSelector().connect(channel, this);
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
// close the socket channel
|
||||
if (channel != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException ignore)
|
||||
{
|
||||
LOG.ignore(ignore);
|
||||
}
|
||||
}
|
||||
|
||||
// notify the future
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = Log.getLogger(ConnectionManager.class);
|
||||
|
||||
public static InetSocketAddress toSocketAddress(URI uri)
|
||||
{
|
||||
if (!uri.isAbsolute())
|
||||
|
@ -130,51 +64,7 @@ public class ConnectionManager extends ContainerLifeCycle
|
|||
return new InetSocketAddress(uri.getHost(),port);
|
||||
}
|
||||
|
||||
private final WebSocketClient client;
|
||||
private WebSocketClientSelectorManager selector;
|
||||
|
||||
public ConnectionManager(WebSocketClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public ConnectPromise connect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
||||
{
|
||||
return new PhysicalConnect(client,driver,request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
selector = newWebSocketClientSelectorManager(client);
|
||||
selector.setSslContextFactory(client.getSslContextFactory());
|
||||
selector.setConnectTimeout(client.getConnectTimeout());
|
||||
addBean(selector);
|
||||
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
removeBean(selector);
|
||||
}
|
||||
|
||||
public WebSocketClientSelectorManager getSelector()
|
||||
{
|
||||
return selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for new WebSocketClientSelectorManager (used by other projects like cometd)
|
||||
*
|
||||
* @param client
|
||||
* the client used to create the WebSocketClientSelectorManager
|
||||
* @return the new WebSocketClientSelectorManager
|
||||
*/
|
||||
protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
|
||||
{
|
||||
return new WebSocketClientSelectorManager(client);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,391 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client.io;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeException;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||
import org.eclipse.jetty.websocket.common.SessionFactory;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
|
||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
|
||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
|
||||
|
||||
/**
|
||||
* This is the initial connection handling that exists immediately after physical connection is established to
|
||||
* destination server.
|
||||
* <p>
|
||||
* Eventually, upon successful Upgrade request/response, this connection swaps itself out for the
|
||||
* WebSocektClientConnection handler.
|
||||
*/
|
||||
public class UpgradeConnection extends AbstractConnection implements Connection.UpgradeFrom
|
||||
{
|
||||
public class SendUpgradeRequest extends FutureCallback implements Runnable
|
||||
{
|
||||
private final Logger LOG = Log.getLogger(UpgradeConnection.SendUpgradeRequest.class);
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
URI uri = connectPromise.getRequest().getRequestURI();
|
||||
request.setRequestURI(uri);
|
||||
|
||||
UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
|
||||
if (handshakeListener != null)
|
||||
{
|
||||
handshakeListener.onHandshakeRequest(request);
|
||||
}
|
||||
|
||||
String rawRequest = request.generate();
|
||||
|
||||
ByteBuffer buf = BufferUtil.toBuffer(rawRequest,StandardCharsets.UTF_8);
|
||||
getEndPoint().write(this,buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Upgrade Request Write Success");
|
||||
}
|
||||
// Writing the request header is complete.
|
||||
super.succeeded();
|
||||
state = State.RESPONSE;
|
||||
// start the interest in fill
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable cause)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Upgrade Request Write Failure",cause);
|
||||
}
|
||||
super.failed(cause);
|
||||
state = State.FAILURE;
|
||||
// Fail the connect promise when a fundamental exception during connect occurs.
|
||||
connectPromise.failed(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/** HTTP Response Code: 101 Switching Protocols */
|
||||
private static final int SWITCHING_PROTOCOLS = 101;
|
||||
|
||||
private enum State
|
||||
{
|
||||
REQUEST,
|
||||
RESPONSE,
|
||||
FAILURE,
|
||||
UPGRADE
|
||||
}
|
||||
|
||||
private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
|
||||
private final ByteBufferPool bufferPool;
|
||||
private final ConnectPromise connectPromise;
|
||||
private final HttpResponseHeaderParser parser;
|
||||
private State state = State.REQUEST;
|
||||
private ClientUpgradeRequest request;
|
||||
private ClientUpgradeResponse response;
|
||||
|
||||
public UpgradeConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise)
|
||||
{
|
||||
super(endp,executor);
|
||||
this.connectPromise = connectPromise;
|
||||
this.bufferPool = connectPromise.getClient().getBufferPool();
|
||||
this.request = connectPromise.getRequest();
|
||||
|
||||
// Setup the parser
|
||||
this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
|
||||
}
|
||||
|
||||
public void disconnect(boolean onlyOutput)
|
||||
{
|
||||
EndPoint endPoint = getEndPoint();
|
||||
// We need to gently close first, to allow
|
||||
// SSL close alerts to be sent by Jetty
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Shutting down output {}",endPoint);
|
||||
}
|
||||
|
||||
endPoint.shutdownOutput();
|
||||
if (!onlyOutput)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Closing {}",endPoint);
|
||||
}
|
||||
endPoint.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void failUpgrade(Throwable cause)
|
||||
{
|
||||
close();
|
||||
connectPromise.failed(cause);
|
||||
}
|
||||
|
||||
private void notifyConnect(ClientUpgradeResponse response)
|
||||
{
|
||||
connectPromise.setResponse(response);
|
||||
|
||||
UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
|
||||
if (handshakeListener != null)
|
||||
{
|
||||
handshakeListener.onHandshakeResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer onUpgradeFrom()
|
||||
{
|
||||
return connectPromise.getResponse().getRemainingBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("onFillable");
|
||||
}
|
||||
ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false);
|
||||
BufferUtil.clear(buffer);
|
||||
try
|
||||
{
|
||||
read(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferPool.release(buffer);
|
||||
}
|
||||
|
||||
if (state == State.RESPONSE)
|
||||
{
|
||||
// Continue Reading
|
||||
fillInterested();
|
||||
}
|
||||
else if (state == State.UPGRADE)
|
||||
{
|
||||
// Stop Reading, upgrade the connection now
|
||||
upgradeConnection(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
getExecutor().execute(new SendUpgradeRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Closed connection {}",this);
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onReadTimeout()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Timeout on connection {}",this);
|
||||
}
|
||||
|
||||
failUpgrade(new IOException("Timeout while performing WebSocket Upgrade"));
|
||||
|
||||
return super.onReadTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read / Parse the waiting read/fill buffer
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer to fill into from the endpoint
|
||||
*/
|
||||
private void read(ByteBuffer buffer)
|
||||
{
|
||||
EndPoint endPoint = getEndPoint();
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int filled = endPoint.fill(buffer);
|
||||
if (filled == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (filled < 0)
|
||||
{
|
||||
LOG.warn("read - EOF Reached");
|
||||
state = State.FAILURE;
|
||||
failUpgrade(new EOFException("Reading WebSocket Upgrade response"));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
|
||||
}
|
||||
response = (ClientUpgradeResponse)parser.parse(buffer);
|
||||
if (response != null)
|
||||
{
|
||||
// Got a response!
|
||||
validateResponse(response);
|
||||
notifyConnect(response);
|
||||
state = State.UPGRADE;
|
||||
return; // do no more reading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException | ParseException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
state = State.FAILURE;
|
||||
UpgradeException ue = new UpgradeException(request.getRequestURI(),e);
|
||||
connectPromise.failed(ue);
|
||||
disconnect(false);
|
||||
}
|
||||
catch (UpgradeException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
state = State.FAILURE;
|
||||
connectPromise.failed(e);
|
||||
disconnect(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeConnection(ClientUpgradeResponse response)
|
||||
{
|
||||
EndPoint endp = getEndPoint();
|
||||
Executor executor = getExecutor();
|
||||
|
||||
EventDriver websocket = connectPromise.getDriver();
|
||||
WebSocketPolicy policy = websocket.getPolicy();
|
||||
|
||||
WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
|
||||
|
||||
SessionFactory sessionFactory = connectPromise.getClient().getSessionFactory();
|
||||
WebSocketSession session = sessionFactory.createSession(request.getRequestURI(),websocket,connection);
|
||||
session.setPolicy(policy);
|
||||
session.setUpgradeRequest(request);
|
||||
session.setUpgradeResponse(response);
|
||||
connection.addListener(session);
|
||||
connectPromise.setSession(session);
|
||||
|
||||
// Initialize / Negotiate Extensions
|
||||
ExtensionStack extensionStack = new ExtensionStack(connectPromise.getClient().getExtensionFactory());
|
||||
extensionStack.negotiate(response.getExtensions());
|
||||
|
||||
extensionStack.configure(connection.getParser());
|
||||
extensionStack.configure(connection.getGenerator());
|
||||
|
||||
// Setup Incoming Routing
|
||||
connection.setNextIncomingFrames(extensionStack);
|
||||
extensionStack.setNextIncoming(session);
|
||||
|
||||
// Setup Outgoing Routing
|
||||
session.setOutgoingHandler(extensionStack);
|
||||
extensionStack.setNextOutgoing(connection);
|
||||
|
||||
session.addManaged(extensionStack);
|
||||
connectPromise.getClient().addManaged(session);
|
||||
|
||||
// Now swap out the connection
|
||||
endp.upgrade(connection);
|
||||
}
|
||||
|
||||
private void validateResponse(ClientUpgradeResponse response)
|
||||
{
|
||||
// Validate Response Status Code
|
||||
if (response.getStatusCode() != SWITCHING_PROTOCOLS)
|
||||
{
|
||||
// TODO: use jetty-http and org.eclipse.jetty.http.HttpStatus for more meaningful exception messages
|
||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Didn't switch protocols, expected status <" + SWITCHING_PROTOCOLS
|
||||
+ ">, but got <" + response.getStatusCode() + ">");
|
||||
}
|
||||
|
||||
// Validate Connection header
|
||||
String connection = response.getHeader("Connection");
|
||||
if (!"upgrade".equalsIgnoreCase(connection))
|
||||
{
|
||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Connection is " + connection + " (expected upgrade)");
|
||||
}
|
||||
|
||||
// Check the Accept hash
|
||||
String reqKey = request.getKey();
|
||||
String expectedHash = AcceptHash.hashKey(reqKey);
|
||||
String respHash = response.getHeader("Sec-WebSocket-Accept");
|
||||
|
||||
response.setSuccess(true);
|
||||
if (expectedHash.equalsIgnoreCase(respHash) == false)
|
||||
{
|
||||
response.setSuccess(false);
|
||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Invalid Sec-WebSocket-Accept hash");
|
||||
}
|
||||
|
||||
// Parse extensions
|
||||
List<ExtensionConfig> extensions = new ArrayList<>();
|
||||
List<String> extValues = response.getHeaders("Sec-WebSocket-Extensions");
|
||||
if (extValues != null)
|
||||
{
|
||||
for (String extVal : extValues)
|
||||
{
|
||||
// TODO use QuotedCSV ???
|
||||
QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(tok.nextToken()));
|
||||
}
|
||||
}
|
||||
}
|
||||
response.setExtensions(extensions);
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
|||
*/
|
||||
public interface UpgradeListener
|
||||
{
|
||||
public void onHandshakeRequest(UpgradeRequest request);
|
||||
void onHandshakeRequest(UpgradeRequest request);
|
||||
|
||||
public void onHandshakeResponse(UpgradeResponse response);
|
||||
void onHandshakeResponse(UpgradeResponse response);
|
||||
}
|
||||
|
|
|
@ -20,15 +20,17 @@ package org.eclipse.jetty.websocket.client.io;
|
|||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.websocket.api.BatchMode;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.api.WriteCallback;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
||||
import org.eclipse.jetty.websocket.client.masks.RandomMasker;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
||||
|
||||
|
@ -37,16 +39,12 @@ import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
|||
*/
|
||||
public class WebSocketClientConnection extends AbstractWebSocketConnection
|
||||
{
|
||||
private final ConnectPromise connectPromise;
|
||||
private final Masker masker;
|
||||
private final AtomicBoolean opened = new AtomicBoolean(false);
|
||||
|
||||
public WebSocketClientConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise, WebSocketPolicy policy)
|
||||
public WebSocketClientConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy websocketPolicy, ByteBufferPool bufferPool)
|
||||
{
|
||||
super(endp,executor,connectPromise.getClient().getScheduler(),policy,connectPromise.getClient().getBufferPool());
|
||||
this.connectPromise = connectPromise;
|
||||
this.masker = connectPromise.getMasker();
|
||||
assert (this.masker != null);
|
||||
super(endp,executor,scheduler,websocketPolicy,bufferPool);
|
||||
this.masker = new RandomMasker();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,18 +58,7 @@ public class WebSocketClientConnection extends AbstractWebSocketConnection
|
|||
{
|
||||
return getEndPoint().getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
boolean beenOpened = opened.getAndSet(true);
|
||||
if (!beenOpened)
|
||||
{
|
||||
connectPromise.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override to set the masker.
|
||||
*/
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
||||
public class WebSocketClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketClientSelectorManager.class);
|
||||
private final WebSocketPolicy policy;
|
||||
private final ByteBufferPool bufferPool;
|
||||
private SslContextFactory sslContextFactory;
|
||||
|
||||
public WebSocketClientSelectorManager(WebSocketClient client)
|
||||
{
|
||||
super(client.getExecutor(),client.getScheduler());
|
||||
this.bufferPool = client.getBufferPool();
|
||||
this.policy = client.getPolicy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SelectableChannel channel, Throwable ex, Object attachment)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Connection Failed",ex);
|
||||
ConnectPromise connect = (ConnectPromise)attachment;
|
||||
connect.failed(ex);
|
||||
}
|
||||
|
||||
public SslContextFactory getSslContextFactory()
|
||||
{
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(final SelectableChannel channel, EndPoint endPoint, final Object attachment) throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("newConnection({},{},{})",channel,endPoint,attachment);
|
||||
ConnectPromise connectPromise = (ConnectPromise)attachment;
|
||||
|
||||
try
|
||||
{
|
||||
String scheme = connectPromise.getRequest().getRequestURI().getScheme();
|
||||
|
||||
if ("wss".equalsIgnoreCase(scheme))
|
||||
{
|
||||
// Encrypted "wss://"
|
||||
SslContextFactory sslContextFactory = getSslContextFactory();
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
SSLEngine engine = newSSLEngine(sslContextFactory,channel);
|
||||
SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
|
||||
Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise);
|
||||
sslEndPoint.setIdleTimeout(connectPromise.getClient().getMaxIdleTimeout());
|
||||
sslEndPoint.setConnection(connection);
|
||||
return sslConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Cannot init SSL");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard "ws://"
|
||||
endPoint.setIdleTimeout(connectPromise.getDriver().getPolicy().getIdleTimeout());
|
||||
return newUpgradeConnection(channel,endPoint,connectPromise);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
connectPromise.failed(e);
|
||||
// rethrow
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("newEndPoint({}, {}, {})",channel,selector,selectionKey);
|
||||
SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
|
||||
endp.setIdleTimeout(policy.getIdleTimeout());
|
||||
return endp;
|
||||
}
|
||||
|
||||
public SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SelectableChannel channel)
|
||||
{
|
||||
String peerHost = null;
|
||||
int peerPort = 0;
|
||||
if (channel instanceof SocketChannel)
|
||||
{
|
||||
SocketChannel sc = (SocketChannel)channel;
|
||||
peerHost = sc.socket().getInetAddress().getHostName();
|
||||
peerPort = sc.socket().getPort();
|
||||
}
|
||||
SSLEngine engine = sslContextFactory.newSSLEngine(peerHost,peerPort);
|
||||
engine.setUseClientMode(true);
|
||||
return engine;
|
||||
}
|
||||
|
||||
public UpgradeConnection newUpgradeConnection(SelectableChannel channel, EndPoint endPoint, ConnectPromise connectPromise)
|
||||
{
|
||||
WebSocketClient client = connectPromise.getClient();
|
||||
Executor executor = client.getExecutor();
|
||||
UpgradeConnection connection = new UpgradeConnection(endPoint,executor,connectPromise);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void setSslContextFactory(SslContextFactory sslContextFactory)
|
||||
{
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
}
|
||||
|
||||
public WebSocketPolicy getPolicy()
|
||||
{
|
||||
return policy;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,14 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
@ -34,9 +41,12 @@ import java.util.concurrent.TimeoutException;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
|
@ -49,8 +59,6 @@ import org.eclipse.jetty.websocket.api.ProtocolException;
|
|||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
||||
import org.eclipse.jetty.websocket.client.io.WebSocketClientSelectorManager;
|
||||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.OpCode;
|
||||
import org.eclipse.jetty.websocket.common.Parser;
|
||||
|
@ -73,10 +81,10 @@ import org.junit.Test;
|
|||
public class ClientCloseTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
||||
|
||||
|
||||
private static class CloseTrackingSocket extends WebSocketAdapter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.CloseTrackingSocket.class);
|
||||
private static final Logger LOG = ClientCloseTest.LOG.getLogger("CloseTrackingSocket");
|
||||
|
||||
public int closeCode = -1;
|
||||
public String closeReason = null;
|
||||
|
@ -147,6 +155,7 @@ public class ClientCloseTest
|
|||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
LOG.debug("onWebSocketConnect({})",session);
|
||||
super.onWebSocketConnect(session);
|
||||
openLatch.countDown();
|
||||
}
|
||||
|
@ -254,42 +263,20 @@ public class ClientCloseTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class TestWebSocketClient extends WebSocketClient
|
||||
public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP
|
||||
{
|
||||
@Override
|
||||
protected ConnectionManager newConnectionManager()
|
||||
protected SelectorManager newSelectorManager(HttpClient client)
|
||||
{
|
||||
return new TestConnectionManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestConnectionManager extends ConnectionManager
|
||||
{
|
||||
public TestConnectionManager(WebSocketClient client)
|
||||
{
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
|
||||
{
|
||||
return new TestSelectorManager(client);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestSelectorManager extends WebSocketClientSelectorManager
|
||||
{
|
||||
public TestSelectorManager(WebSocketClient client)
|
||||
{
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
|
||||
{
|
||||
TestEndPoint endp = new TestEndPoint(channel,selectSet,selectionKey,getScheduler());
|
||||
endp.setIdleTimeout(getPolicy().getIdleTimeout());
|
||||
return endp;
|
||||
return new ClientSelectorManager(client, 1){
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||
{
|
||||
TestEndPoint endPoint = new TestEndPoint(channel,selector,key,getScheduler());
|
||||
endPoint.setIdleTimeout(client.getIdleTimeout());
|
||||
return endPoint;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +301,9 @@ public class ClientCloseTest
|
|||
@Before
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new TestWebSocketClient();
|
||||
HttpClient httpClient = new HttpClient(new TestClientTransportOverHTTP(), null);
|
||||
client = new WebSocketClient(httpClient);
|
||||
client.addBean(httpClient);
|
||||
client.start();
|
||||
}
|
||||
|
||||
|
@ -328,10 +317,7 @@ public class ClientCloseTest
|
|||
@After
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
if (client.isRunning())
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -389,6 +375,7 @@ public class ClientCloseTest
|
|||
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server"));
|
||||
}
|
||||
|
||||
@Ignore("Need sbordet's help here")
|
||||
@Test
|
||||
public void testNetworkCongestion() throws Exception
|
||||
{
|
||||
|
@ -561,7 +548,7 @@ public class ClientCloseTest
|
|||
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(timeout = 5000L)
|
||||
public void testStopLifecycle() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
|
|
|
@ -18,19 +18,24 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.toolchain.test.OS;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
@ -87,7 +92,8 @@ public class ClientConnectTest
|
|||
@Before
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient(bufferPool);
|
||||
client = new WebSocketClient();
|
||||
client.setBufferPool(bufferPool);
|
||||
client.setConnectTimeout(timeout);
|
||||
client.start();
|
||||
}
|
||||
|
@ -124,10 +130,36 @@ public class ClientConnectTest
|
|||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
sess.close();
|
||||
wsocket.waitForConnected(1, TimeUnit.SECONDS);
|
||||
|
||||
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
|
||||
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
|
||||
|
||||
sess.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAltConnect() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
URI wsUri = server.getWsUri();
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
|
||||
WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, wsocket);
|
||||
req.header("X-Foo","Req");
|
||||
CompletableFuture<Session> sess = req.sendAsync();
|
||||
|
||||
sess.thenAccept((s) -> {
|
||||
System.out.printf("Session: %s%n",s);
|
||||
s.close();
|
||||
assertThat("Connect.UpgradeRequest",wsocket.connectUpgradeRequest,notNullValue());
|
||||
assertThat("Connect.UpgradeResponse",wsocket.connectUpgradeResponse,notNullValue());
|
||||
});
|
||||
|
||||
IBlockheadServerConnection connection = server.accept();
|
||||
connection.upgrade();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -141,7 +173,9 @@ public class ClientConnectTest
|
|||
IBlockheadServerConnection connection = server.accept();
|
||||
connection.readRequest();
|
||||
// no upgrade, just fail with a 404 error
|
||||
connection.respond("HTTP/1.1 404 NOT FOUND\r\n\r\n");
|
||||
connection.respond("HTTP/1.1 404 NOT FOUND\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"\r\n");
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
try
|
||||
|
@ -170,7 +204,9 @@ public class ClientConnectTest
|
|||
IBlockheadServerConnection connection = server.accept();
|
||||
connection.readRequest();
|
||||
// Send OK to GET but not upgrade
|
||||
connection.respond("HTTP/1.1 200 OK\r\n\r\n");
|
||||
connection.respond("HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"\r\n");
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
try
|
||||
|
@ -205,6 +241,7 @@ public class ClientConnectTest
|
|||
resp.append("HTTP/1.1 200 OK\r\n"); // intentionally 200 (not 101)
|
||||
// Include a value accept key
|
||||
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
|
||||
resp.append("Content-Length: 0\r\n");
|
||||
resp.append("\r\n");
|
||||
connection.respond(resp.toString());
|
||||
|
||||
|
|
|
@ -18,15 +18,19 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -51,18 +55,33 @@ public class CookieTest
|
|||
{
|
||||
public EventQueue<String> messageQueue = new EventQueue<>();
|
||||
public EventQueue<Throwable> errorQueue = new EventQueue<>();
|
||||
private CountDownLatch openLatch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session sess)
|
||||
{
|
||||
openLatch.countDown();
|
||||
super.onWebSocketConnect(sess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
System.err.printf("onTEXT - %s%n",message);
|
||||
messageQueue.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable cause)
|
||||
{
|
||||
System.err.printf("onERROR - %s%n",cause);
|
||||
errorQueue.add(cause);
|
||||
}
|
||||
|
||||
public void awaitOpen(int duration, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
assertTrue("Open Latch", openLatch.await(duration,unit));
|
||||
}
|
||||
}
|
||||
|
||||
private WebSocketClient client;
|
||||
|
@ -125,7 +144,7 @@ public class CookieTest
|
|||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||
|
||||
assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
|
||||
assertThat("Cookies seen at server side",serverCookies,containsString("foo=\"bar is the word\""));
|
||||
assertThat("Cookies seen at server side",serverCookies,containsString("foo=bar is the word"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,7 +168,7 @@ public class CookieTest
|
|||
// client confirms upgrade and receipt of frame
|
||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||
|
||||
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
|
||||
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
|
||||
}
|
||||
|
||||
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, IBlockheadServerConnection serverConn)
|
||||
|
@ -164,18 +183,29 @@ public class CookieTest
|
|||
serverCookieFrame.setFin(true);
|
||||
serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
|
||||
serverConn.write(serverCookieFrame);
|
||||
|
||||
// Server closes connection
|
||||
serverConn.close(StatusCode.NORMAL);
|
||||
serverConn.flush();
|
||||
|
||||
// Confirm client connect on future
|
||||
clientConnectFuture.get(30000,TimeUnit.MILLISECONDS);
|
||||
|
||||
// Wait for client receipt of cookie frame via client websocket
|
||||
clientSocket.messageQueue.awaitEventCount(1,2,TimeUnit.SECONDS);
|
||||
clientConnectFuture.get(10,TimeUnit.SECONDS);
|
||||
clientSocket.awaitOpen(2,TimeUnit.SECONDS);
|
||||
|
||||
try
|
||||
{
|
||||
// Wait for client receipt of cookie frame via client websocket
|
||||
clientSocket.messageQueue.awaitEventCount(1, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
assertThat("Message Count", clientSocket.messageQueue.size(), is(1));
|
||||
}
|
||||
|
||||
String cookies = clientSocket.messageQueue.poll();
|
||||
LOG.debug("Cookies seen at server: {}",cookies);
|
||||
|
||||
// Server closes connection
|
||||
serverConn.close(StatusCode.NORMAL);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.websocket.client;
|
|||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Exchanger;
|
||||
|
@ -29,9 +31,9 @@ import java.util.concurrent.TimeoutException;
|
|||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.junit.Assert;
|
||||
|
||||
|
@ -84,16 +86,19 @@ public class JettyTrackingSocket extends WebSocketAdapter
|
|||
|
||||
public void assertNotClosed()
|
||||
{
|
||||
LOG.debug("assertNotClosed() - {}", closeLatch.getCount());
|
||||
Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||
}
|
||||
|
||||
public void assertNotOpened()
|
||||
{
|
||||
LOG.debug("assertNotOpened() - {}", openLatch.getCount());
|
||||
Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||
}
|
||||
|
||||
public void assertWasOpened() throws InterruptedException
|
||||
{
|
||||
LOG.debug("assertWasOpened() - {}", openLatch.getCount());
|
||||
Assert.assertThat("Was Opened",openLatch.await(30,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
|
@ -128,6 +133,7 @@ public class JettyTrackingSocket extends WebSocketAdapter
|
|||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
super.onWebSocketConnect(session);
|
||||
assertThat("Session", session, notNullValue());
|
||||
connectUpgradeRequest = session.getUpgradeRequest();
|
||||
connectUpgradeResponse = session.getUpgradeResponse();
|
||||
openLatch.countDown();
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test various init techniques for WebSocketClient
|
||||
*/
|
||||
public class WebSocketClientInitTest
|
||||
{
|
||||
/**
|
||||
* This is the new Jetty 9.4 advanced usage mode of WebSocketClient,
|
||||
* that allows for more robust HTTP configurations (such as authentication,
|
||||
* cookies, and proxies)
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testInit_HttpClient_StartedOutside() throws Exception
|
||||
{
|
||||
HttpClient http = new HttpClient();
|
||||
http.start();
|
||||
|
||||
try
|
||||
{
|
||||
WebSocketClient ws = new WebSocketClient(http);
|
||||
ws.start();
|
||||
try
|
||||
{
|
||||
assertThat("HttpClient",ws.getHttpClient(),is(http));
|
||||
|
||||
assertThat("WebSocketClient started",ws.isStarted(),is(true));
|
||||
assertThat("HttpClient started",http.isStarted(),is(true));
|
||||
|
||||
HttpClient httpBean = ws.getBean(HttpClient.class);
|
||||
assertThat("HttpClient should not be found in WebSocketClient",httpBean,nullValue());
|
||||
assertThat("HttpClient bean is managed",ws.isManaged(httpBean),is(false));
|
||||
assertThat("WebSocketClient should not be found in HttpClient",http.getBean(WebSocketClient.class),nullValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
ws.stop();
|
||||
}
|
||||
assertThat("WebSocketClient stopped",ws.isStopped(),is(true));
|
||||
assertThat("HttpClient stopped",http.isStopped(),is(false));
|
||||
}
|
||||
finally
|
||||
{
|
||||
http.stop();
|
||||
}
|
||||
|
||||
assertThat("HttpClient stopped",http.isStopped(),is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the backward compatibility mode of WebSocketClient.
|
||||
* This is also the primary mode that JSR356 Standalone WebSocket Client is initialized.
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testInit_HttpClient_SyntheticStart() throws Exception
|
||||
{
|
||||
HttpClient http = null;
|
||||
WebSocketClient ws = new WebSocketClient();
|
||||
ws.start();
|
||||
try
|
||||
{
|
||||
http = ws.getHttpClient();
|
||||
|
||||
assertThat("WebSocketClient started",ws.isStarted(),is(true));
|
||||
assertThat("HttpClient started",http.isStarted(),is(true));
|
||||
|
||||
HttpClient httpBean = ws.getBean(HttpClient.class);
|
||||
assertThat("HttpClient bean found in WebSocketClient",httpBean,is(http));
|
||||
assertThat("HttpClient bean is managed",ws.isManaged(httpBean),is(true));
|
||||
assertThat("WebSocketClient should not be found in HttpClient",http.getBean(WebSocketClient.class),nullValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
ws.stop();
|
||||
}
|
||||
|
||||
assertThat("WebSocketClient stopped",ws.isStopped(),is(true));
|
||||
assertThat("HttpClient stopped",http.isStopped(),is(true));
|
||||
}
|
||||
}
|
|
@ -18,7 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.client;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
|
@ -50,329 +53,263 @@ import org.junit.runner.RunWith;
|
|||
public class WebSocketClientTest
|
||||
{
|
||||
private BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@Before
|
||||
public void startServer() throws Exception
|
||||
public void startClientServer() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.start();
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopServer() throws Exception
|
||||
public void stopClientServer() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddExtension_NotInstalled() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
request.addExtensions("x-bad");
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
request.addExtensions("x-bad");
|
||||
|
||||
// Should trigger failure on bad extension
|
||||
client.connect(cliSock,wsUri,request);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
// Should trigger failure on bad extension
|
||||
client.connect(cliSock,wsUri,request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicEcho_FromClient() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
|
||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||
Collection<WebSocketSession> sessions = client.getOpenSessions();
|
||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||
|
||||
RemoteEndpoint remote = cliSock.getSession().getRemote();
|
||||
remote.sendStringByFuture("Hello World!");
|
||||
if (remote.getBatchMode() == BatchMode.ON)
|
||||
remote.flush();
|
||||
srvSock.echoMessage(1,30,TimeUnit.SECONDS);
|
||||
// wait for response from server
|
||||
cliSock.waitForMessage(30,TimeUnit.SECONDS);
|
||||
RemoteEndpoint remote = cliSock.getSession().getRemote();
|
||||
remote.sendStringByFuture("Hello World!");
|
||||
if (remote.getBatchMode() == BatchMode.ON)
|
||||
remote.flush();
|
||||
srvSock.echoMessage(1,30,TimeUnit.SECONDS);
|
||||
// wait for response from server
|
||||
cliSock.waitForMessage(30,TimeUnit.SECONDS);
|
||||
|
||||
cliSock.assertMessage("Hello World!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
cliSock.assertMessage("Hello World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicEcho_UsingCallback() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
client.setMaxIdleTimeout(160000);
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||
|
||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||
FutureWriteCallback callback = new FutureWriteCallback();
|
||||
|
||||
FutureWriteCallback callback = new FutureWriteCallback();
|
||||
|
||||
cliSock.getSession().getRemote().sendString("Hello World!",callback);
|
||||
callback.get(1,TimeUnit.SECONDS);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
cliSock.getSession().getRemote().sendString("Hello World!",callback);
|
||||
callback.get(1,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicEcho_FromServer() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
Future<Session> future = client.connect(wsocket,server.getWsUri());
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
Future<Session> future = client.connect(wsocket,server.getWsUri());
|
||||
|
||||
// Server
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
// Server
|
||||
final IBlockheadServerConnection srvSock = server.accept();
|
||||
srvSock.upgrade();
|
||||
|
||||
// Validate connect
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
// Validate connect
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
|
||||
// Have server send initial message
|
||||
srvSock.write(new TextFrame().setPayload("Hello World"));
|
||||
// Have server send initial message
|
||||
srvSock.write(new TextFrame().setPayload("Hello World"));
|
||||
|
||||
// Verify connect
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
wsocket.assertWasOpened();
|
||||
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
|
||||
// Verify connect
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
wsocket.assertWasOpened();
|
||||
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
|
||||
|
||||
wsocket.assertMessage("Hello World");
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
wsocket.assertMessage("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalRemoteAddress() throws Exception
|
||||
{
|
||||
WebSocketClient fact = new WebSocketClient();
|
||||
fact.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = fact.connect(wsocket,wsUri);
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
InetSocketAddress local = wsocket.getSession().getLocalAddress();
|
||||
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
|
||||
InetSocketAddress local = wsocket.getSession().getLocalAddress();
|
||||
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
|
||||
|
||||
Assert.assertThat("Local Socket Address",local,notNullValue());
|
||||
Assert.assertThat("Remote Socket Address",remote,notNullValue());
|
||||
Assert.assertThat("Local Socket Address",local,notNullValue());
|
||||
Assert.assertThat("Remote Socket Address",remote,notNullValue());
|
||||
|
||||
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
|
||||
Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
|
||||
Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
|
||||
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
|
||||
Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
|
||||
Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
|
||||
|
||||
Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
|
||||
Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
fact.stop();
|
||||
}
|
||||
Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
|
||||
Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageBiggerThanBufferSize() throws Exception
|
||||
{
|
||||
WebSocketClient factSmall = new WebSocketClient();
|
||||
factSmall.start();
|
||||
try
|
||||
int bufferSize = 512;
|
||||
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
|
||||
ssocket.write(0x80 | 0x01); // FIN + TEXT
|
||||
ssocket.write(0x7E); // No MASK and 2 bytes length
|
||||
ssocket.write(length >> 8); // first length byte
|
||||
ssocket.write(length & 0xFF); // second length byte
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
int bufferSize = 512;
|
||||
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = factSmall.connect(wsocket,wsUri);
|
||||
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
|
||||
ssocket.write(0x80 | 0x01); // FIN + TEXT
|
||||
ssocket.write(0x7E); // No MASK and 2 bytes length
|
||||
ssocket.write(length >> 8); // first length byte
|
||||
ssocket.write(length & 0xFF); // second length byte
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
ssocket.write('x');
|
||||
}
|
||||
ssocket.flush();
|
||||
|
||||
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
factSmall.stop();
|
||||
ssocket.write('x');
|
||||
}
|
||||
ssocket.flush();
|
||||
|
||||
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that <code>@WebSocket(maxTextMessageSize = 100*1024)</code> behaves as expected.
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testMaxMessageSize() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
MaxMessageSocket wsocket = new MaxMessageSocket();
|
||||
MaxMessageSocket wsocket = new MaxMessageSocket();
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
|
||||
wsocket.awaitConnect(1,TimeUnit.SECONDS);
|
||||
wsocket.awaitConnect(1,TimeUnit.SECONDS);
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
Assert.assertThat("Session",sess,notNullValue());
|
||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||
|
||||
// Create string that is larger than default size of 64k
|
||||
// but smaller than maxMessageSize of 100k
|
||||
byte buf[] = new byte[80 * 1024];
|
||||
Arrays.fill(buf,(byte)'x');
|
||||
String msg = StringUtil.toUTF8String(buf,0,buf.length);
|
||||
// Create string that is larger than default size of 64k
|
||||
// but smaller than maxMessageSize of 100k
|
||||
byte buf[] = new byte[80 * 1024];
|
||||
Arrays.fill(buf,(byte)'x');
|
||||
String msg = StringUtil.toUTF8String(buf,0,buf.length);
|
||||
|
||||
wsocket.getSession().getRemote().sendStringByFuture(msg);
|
||||
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
|
||||
// wait for response from server
|
||||
wsocket.waitForMessage(1,TimeUnit.SECONDS);
|
||||
wsocket.getSession().getRemote().sendStringByFuture(msg);
|
||||
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
|
||||
// wait for response from server
|
||||
wsocket.waitForMessage(1,TimeUnit.SECONDS);
|
||||
|
||||
wsocket.assertMessage(msg);
|
||||
wsocket.assertMessage(msg);
|
||||
|
||||
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterMap() throws Exception
|
||||
{
|
||||
WebSocketClient fact = new WebSocketClient();
|
||||
fact.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
|
||||
Future<Session> future = fact.connect(wsocket,wsUri);
|
||||
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
IBlockheadServerConnection ssocket = server.accept();
|
||||
ssocket.upgrade();
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
Session session = wsocket.getSession();
|
||||
UpgradeRequest req = session.getUpgradeRequest();
|
||||
Assert.assertThat("Upgrade Request",req,notNullValue());
|
||||
Session session = wsocket.getSession();
|
||||
UpgradeRequest req = session.getUpgradeRequest();
|
||||
Assert.assertThat("Upgrade Request",req,notNullValue());
|
||||
|
||||
Map<String, List<String>> parameterMap = req.getParameterMap();
|
||||
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
|
||||
Map<String, List<String>> parameterMap = req.getParameterMap();
|
||||
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
|
||||
|
||||
Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[]
|
||||
{ "cashews" })));
|
||||
Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[]
|
||||
{ "handful" })));
|
||||
Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[]
|
||||
{ "off" })));
|
||||
Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[] { "cashews" })));
|
||||
Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[] { "handful" })));
|
||||
Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[] { "off" })));
|
||||
|
||||
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
fact.stop();
|
||||
}
|
||||
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
org.eclipse.jetty.LEVEL=WARN
|
||||
# org.eclipse.jetty.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.SelectChannelEndPoint.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.IdleTimeout.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.io.LEVEL=INFO
|
||||
# org.eclipse.jetty.client.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.util.LEVEL=INFO
|
||||
# org.eclipse.jetty.websocket.LEVEL=WARN
|
||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.client.ClientCloseTest.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
|
||||
|
||||
# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
|
||||
# org.eclipse.jetty.websocket.common.test.BlockheadServerConnection.LEVEL=DEBUG
|
||||
|
||||
### Hide the stacktraces during testing
|
||||
org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false
|
||||
|
|
|
@ -404,21 +404,42 @@ public class Parser
|
|||
if (isRsv1InUse())
|
||||
frame.setRsv1(true);
|
||||
else
|
||||
throw new ProtocolException("RSV1 not allowed to be set");
|
||||
{
|
||||
String err = "RSV1 not allowed to be set";
|
||||
if(LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||
}
|
||||
throw new ProtocolException(err);
|
||||
}
|
||||
}
|
||||
if ((b & 0x20) != 0)
|
||||
{
|
||||
if (isRsv2InUse())
|
||||
frame.setRsv2(true);
|
||||
else
|
||||
throw new ProtocolException("RSV2 not allowed to be set");
|
||||
{
|
||||
String err = "RSV2 not allowed to be set";
|
||||
if(LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||
}
|
||||
throw new ProtocolException(err);
|
||||
}
|
||||
}
|
||||
if ((b & 0x10) != 0)
|
||||
{
|
||||
if (isRsv3InUse())
|
||||
frame.setRsv3(true);
|
||||
else
|
||||
throw new ProtocolException("RSV3 not allowed to be set");
|
||||
{
|
||||
String err = "RSV3 not allowed to be set";
|
||||
if(LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||
}
|
||||
throw new ProtocolException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.common;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
|
||||
public class UpgradeRequestAdapter implements UpgradeRequest
|
||||
{
|
||||
private URI requestURI;
|
||||
private List<String> subProtocols = new ArrayList<>(1);
|
||||
private List<ExtensionConfig> extensions = new ArrayList<>(1);
|
||||
private List<HttpCookie> cookies = new ArrayList<>(1);
|
||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private Map<String, List<String>> parameters = new HashMap<>(1);
|
||||
private Object session;
|
||||
private String httpVersion;
|
||||
private String method;
|
||||
private String host;
|
||||
private boolean secure;
|
||||
|
||||
protected UpgradeRequestAdapter()
|
||||
{
|
||||
/* anonymous, no requestURI, upgrade request */
|
||||
}
|
||||
|
||||
public UpgradeRequestAdapter(String requestURI)
|
||||
{
|
||||
this(URI.create(requestURI));
|
||||
}
|
||||
|
||||
public UpgradeRequestAdapter(URI requestURI)
|
||||
{
|
||||
setRequestURI(requestURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(ExtensionConfig... configs)
|
||||
{
|
||||
Collections.addAll(extensions, configs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(String... configs)
|
||||
{
|
||||
for (String config : configs)
|
||||
{
|
||||
extensions.add(ExtensionConfig.parse(config));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaders()
|
||||
{
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
List<String> values = headers.get(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return values.get(0);
|
||||
}
|
||||
// join it with commas
|
||||
boolean needsDelim = false;
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (String value : values)
|
||||
{
|
||||
if (needsDelim)
|
||||
{
|
||||
ret.append(", ");
|
||||
}
|
||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||
needsDelim = true;
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeaderInt(String name)
|
||||
{
|
||||
List<String> values = headers.get(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return Integer.parseInt(values.get(0));
|
||||
}
|
||||
throw new NumberFormatException("Cannot convert multi-value header into int");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpVersion()
|
||||
{
|
||||
return httpVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOrigin()
|
||||
{
|
||||
return getHeader("Origin");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of the query parameters of the request.
|
||||
*
|
||||
* @return a unmodifiable map of query parameters of the request.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<String>> getParameterMap()
|
||||
{
|
||||
return Collections.unmodifiableMap(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocolVersion()
|
||||
{
|
||||
String version = getHeader("Sec-WebSocket-Version");
|
||||
if (version == null)
|
||||
{
|
||||
return "13"; // Default
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
return requestURI.getQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI()
|
||||
{
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the Servlet HTTP Session (if present)
|
||||
* <p>
|
||||
* Note: Never present on a Client UpgradeRequest.
|
||||
*
|
||||
* @return the Servlet HTTPSession on server side UpgradeRequests
|
||||
*/
|
||||
@Override
|
||||
public Object getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
return subProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User Principal for this request.
|
||||
* <p>
|
||||
* Only applicable when using UpgradeRequest from server side.
|
||||
*
|
||||
* @return the user principal
|
||||
*/
|
||||
@Override
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
// Server side should override to implement
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubProtocol(String test)
|
||||
{
|
||||
for (String protocol : subProtocols)
|
||||
{
|
||||
if (protocol.equalsIgnoreCase(test))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOrigin(String test)
|
||||
{
|
||||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
this.cookies.clear();
|
||||
if (cookies != null && !cookies.isEmpty())
|
||||
{
|
||||
this.cookies.addAll(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
this.extensions.clear();
|
||||
if (configs != null)
|
||||
{
|
||||
this.extensions.addAll(configs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, List<String> values)
|
||||
{
|
||||
headers.put(name,values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
setHeader(name,values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaders(Map<String, List<String>> headers)
|
||||
{
|
||||
clearHeaders();
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
||||
{
|
||||
String name = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
setHeader(name,values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHttpVersion(String httpVersion)
|
||||
{
|
||||
this.httpVersion = httpVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMethod(String method)
|
||||
{
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
protected void setParameterMap(Map<String, List<String>> parameters)
|
||||
{
|
||||
this.parameters.clear();
|
||||
this.parameters.putAll(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestURI(URI uri)
|
||||
{
|
||||
this.requestURI = uri;
|
||||
String scheme = uri.getScheme();
|
||||
if ("ws".equalsIgnoreCase(scheme))
|
||||
{
|
||||
secure = false;
|
||||
}
|
||||
else if ("wss".equalsIgnoreCase(scheme))
|
||||
{
|
||||
secure = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
|
||||
}
|
||||
this.host = this.requestURI.getHost();
|
||||
this.parameters.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSession(Object session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(List<String> subProtocols)
|
||||
{
|
||||
this.subProtocols.clear();
|
||||
if (subProtocols != null)
|
||||
{
|
||||
this.subProtocols.addAll(subProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Sub Protocol request list.
|
||||
*
|
||||
* @param protocols
|
||||
* the sub protocols desired
|
||||
*/
|
||||
@Override
|
||||
public void setSubProtocols(String... protocols)
|
||||
{
|
||||
subProtocols.clear();
|
||||
Collections.addAll(subProtocols, protocols);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
|
||||
public class UpgradeResponseAdapter implements UpgradeResponse
|
||||
{
|
||||
public static final String SEC_WEBSOCKET_PROTOCOL = WebSocketConstants.SEC_WEBSOCKET_PROTOCOL;
|
||||
private int statusCode;
|
||||
private String statusReason;
|
||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private List<ExtensionConfig> extensions = new ArrayList<>();
|
||||
private boolean success = false;
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value)
|
||||
{
|
||||
String key = name;
|
||||
List<String> values = headers.get(key);
|
||||
if (values == null)
|
||||
{
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
values.add(value);
|
||||
headers.put(key,values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the accepted WebSocket protocol.
|
||||
*
|
||||
* @return the accepted WebSocket protocol.
|
||||
*/
|
||||
@Override
|
||||
public String getAcceptedSubProtocol()
|
||||
{
|
||||
return getHeader(SEC_WEBSOCKET_PROTOCOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of extensions that should be used for the websocket.
|
||||
*
|
||||
* @return the list of negotiated extensions to use.
|
||||
*/
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
List<String> values = getHeaders(name);
|
||||
// no value list
|
||||
if (values == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int size = values.size();
|
||||
// empty value list
|
||||
if (size <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// simple return
|
||||
if (size == 1)
|
||||
{
|
||||
return values.get(0);
|
||||
}
|
||||
// join it with commas
|
||||
boolean needsDelim = false;
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (String value : values)
|
||||
{
|
||||
if (needsDelim)
|
||||
{
|
||||
ret.append(", ");
|
||||
}
|
||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||
needsDelim = true;
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getHeaderNames()
|
||||
{
|
||||
return headers.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusReason()
|
||||
{
|
||||
return statusReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess()
|
||||
{
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a forbidden upgrade response.
|
||||
* <p>
|
||||
* This means that the websocket endpoint was valid, but the conditions to use a WebSocket resulted in a forbidden
|
||||
* access.
|
||||
* <p>
|
||||
* Use this when the origin or authentication is invalid.
|
||||
*
|
||||
* @param message
|
||||
* the short 1 line detail message about the forbidden response
|
||||
* @throws IOException
|
||||
* if unable to send the forbidden
|
||||
*/
|
||||
@Override
|
||||
public void sendForbidden(String message) throws IOException
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the accepted WebSocket Protocol.
|
||||
*
|
||||
* @param protocol
|
||||
* the protocol to list as accepted
|
||||
*/
|
||||
@Override
|
||||
public void setAcceptedSubProtocol(String protocol)
|
||||
{
|
||||
setHeader(SEC_WEBSOCKET_PROTOCOL,protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of extensions that are approved for use with this websocket.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
|
||||
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the complete list of extensions that are
|
||||
* available in this WebSocket server implementation.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param extensions
|
||||
* the list of extensions to use.
|
||||
*/
|
||||
@Override
|
||||
public void setExtensions(List<ExtensionConfig> extensions)
|
||||
{
|
||||
this.extensions.clear();
|
||||
if (extensions != null)
|
||||
{
|
||||
this.extensions.addAll(extensions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
headers.put(name,values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusCode(int statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusReason(String statusReason)
|
||||
{
|
||||
this.statusReason = statusReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuccess(boolean success)
|
||||
{
|
||||
this.success = success;
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
@ -70,6 +71,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
private final LogicalConnection connection;
|
||||
private final EventDriver websocket;
|
||||
private final Executor executor;
|
||||
private final WebSocketPolicy policy;
|
||||
private ClassLoader classLoader;
|
||||
private ExtensionFactory extensionFactory;
|
||||
private RemoteEndpointFactory remoteEndpointFactory;
|
||||
|
@ -78,9 +80,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
private RemoteEndpoint remote;
|
||||
private IncomingFrames incomingHandler;
|
||||
private OutgoingFrames outgoingHandler;
|
||||
private WebSocketPolicy policy;
|
||||
private UpgradeRequest upgradeRequest;
|
||||
private UpgradeResponse upgradeResponse;
|
||||
private CompletableFuture<Session> openFuture;
|
||||
|
||||
public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection)
|
||||
{
|
||||
|
@ -96,7 +98,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
this.outgoingHandler = connection;
|
||||
this.incomingHandler = websocket;
|
||||
this.connection.getIOState().addListener(this);
|
||||
this.policy = containerScope.getPolicy();
|
||||
this.policy = websocket.getPolicy();
|
||||
|
||||
addBean(this.connection);
|
||||
addBean(this.websocket);
|
||||
|
@ -348,16 +350,13 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
}
|
||||
|
||||
/**
|
||||
* Incoming Errors from Parser
|
||||
* Incoming Errors
|
||||
*/
|
||||
@Override
|
||||
public void incomingError(Throwable t)
|
||||
{
|
||||
if (connection.getIOState().isInputAvailable())
|
||||
{
|
||||
// Forward Errors to User WebSocket Object
|
||||
websocket.incomingError(t);
|
||||
}
|
||||
// Forward Errors to User WebSocket Object
|
||||
websocket.incomingError(t);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -416,6 +415,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
|
||||
public void notifyError(Throwable cause)
|
||||
{
|
||||
if (openFuture != null && !openFuture.isDone())
|
||||
openFuture.completeExceptionally(cause);
|
||||
incomingError(cause);
|
||||
}
|
||||
|
||||
|
@ -509,6 +510,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
{
|
||||
LOG.debug("open -> {}",dump());
|
||||
}
|
||||
|
||||
if(openFuture != null)
|
||||
{
|
||||
openFuture.complete(this);
|
||||
}
|
||||
}
|
||||
catch (CloseException ce)
|
||||
{
|
||||
|
@ -534,6 +540,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
this.extensionFactory = extensionFactory;
|
||||
}
|
||||
|
||||
public void setFuture(CompletableFuture<Session> fut)
|
||||
{
|
||||
this.openFuture = fut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout in milliseconds
|
||||
*/
|
||||
|
@ -548,9 +559,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
|||
this.outgoingHandler = outgoing;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setPolicy(WebSocketPolicy policy)
|
||||
{
|
||||
this.policy = policy;
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void setUpgradeRequest(UpgradeRequest request)
|
||||
|
|
|
@ -234,7 +234,7 @@ public abstract class AbstractEventDriver extends AbstractLifeCycle implements I
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
unhandled(t);
|
||||
this.session.notifyError(t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ public class JettyListenerEventDriver extends AbstractEventDriver
|
|||
public void onConnect()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onConnect()");
|
||||
LOG.debug("onConnect({})", session);
|
||||
listener.onWebSocketConnect(session);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,10 +75,16 @@ public abstract class AbstractExtension extends AbstractLifeCycle implements Dum
|
|||
out.append(bean.toString());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void init(WebSocketContainerScope container)
|
||||
{
|
||||
this.policy = container.getPolicy();
|
||||
this.bufferPool = container.getBufferPool();
|
||||
init(container.getPolicy(),container.getBufferPool());
|
||||
}
|
||||
|
||||
public void init(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
||||
{
|
||||
this.policy = policy;
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
public ByteBufferPool getBufferPool()
|
||||
|
|
|
@ -811,6 +811,11 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
|||
@Override
|
||||
public void onUpgradeTo(ByteBuffer prefilled)
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("onUpgradeTo({})", BufferUtil.toDetailString(prefilled));
|
||||
}
|
||||
|
||||
setInitialBuffer(prefilled);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -422,7 +422,7 @@ public class IOState
|
|||
public void onOpened()
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug(" onOpened()");
|
||||
LOG.debug("onOpened()");
|
||||
|
||||
ConnectionState event = null;
|
||||
synchronized (this)
|
||||
|
|
|
@ -40,6 +40,7 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
|
|||
public SimpleContainerScope(WebSocketPolicy policy)
|
||||
{
|
||||
this(policy,new MappedByteBufferPool(),new DecoratedObjectFactory());
|
||||
this.sslContextFactory = new SslContextFactory();
|
||||
}
|
||||
|
||||
public SimpleContainerScope(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
||||
|
@ -54,7 +55,7 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
|
|||
this.objectFactory = objectFactory;
|
||||
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
String name = "WebSocketSimpleContainer@" + hashCode();
|
||||
String name = SimpleContainerScope.class.getSimpleName() + ".Executor@" + hashCode();
|
||||
threadPool.setName(name);
|
||||
threadPool.setDaemon(true);
|
||||
this.executor = threadPool;
|
||||
|
@ -106,14 +107,16 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
|
|||
{
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSessionOpened(WebSocketSession session)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionClosed(WebSocketSession session)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -542,6 +542,7 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
|||
StringBuilder resp = new StringBuilder();
|
||||
resp.append("HTTP/1.1 101 Upgrade\r\n");
|
||||
resp.append("Connection: upgrade\r\n");
|
||||
resp.append("Content-Length: 0\r\n");
|
||||
resp.append("Sec-WebSocket-Accept: ");
|
||||
resp.append(AcceptHash.hashKey(key)).append("\r\n");
|
||||
if (extensionStack.hasNegotiatedExtensions())
|
||||
|
@ -571,7 +572,7 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
|||
}
|
||||
}
|
||||
resp.append("\r\n");
|
||||
|
||||
|
||||
// Write Response
|
||||
LOG.debug("Response: {}",resp.toString());
|
||||
write(resp.toString().getBytes());
|
||||
|
@ -611,4 +612,4 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
|||
getOutputStream().write(arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -576,7 +576,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
|||
|
||||
// Setup Session
|
||||
WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
|
||||
session.setPolicy(driver.getPolicy());
|
||||
session.setUpgradeRequest(request);
|
||||
// set true negotiated extension list back to response
|
||||
response.setExtensions(extensionStack.getNegotiatedExtensions());
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -30,23 +34,17 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
|||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
|
||||
import org.eclipse.jetty.websocket.common.util.Sha1Sum;
|
||||
import org.eclipse.jetty.websocket.server.helper.CaptureSocket;
|
||||
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class PerMessageDeflateExtensionTest
|
||||
{
|
||||
|
@ -83,9 +81,6 @@ public class PerMessageDeflateExtensionTest
|
|||
return modes;
|
||||
}
|
||||
|
||||
@Rule
|
||||
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
|
||||
|
||||
private SimpleServletServer server;
|
||||
private String scheme;
|
||||
private int msgSize;
|
||||
|
@ -132,7 +127,7 @@ public class PerMessageDeflateExtensionTest
|
|||
serverPolicy.setMaxBinaryMessageSize(binBufferSize);
|
||||
serverPolicy.setMaxBinaryMessageBufferSize(binBufferSize);
|
||||
|
||||
WebSocketClient client = new WebSocketClient(server.getSslContextFactory(),null,bufferPool);
|
||||
WebSocketClient client = new WebSocketClient(server.getSslContextFactory());
|
||||
WebSocketPolicy clientPolicy = client.getPolicy();
|
||||
clientPolicy.setMaxBinaryMessageSize(binBufferSize);
|
||||
clientPolicy.setMaxBinaryMessageBufferSize(binBufferSize);
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
||||
import org.eclipse.jetty.websocket.server.helper.EchoSocket;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
|
|
|
@ -324,6 +324,7 @@ public class WebSocketCloseTest
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
private void fastClose() throws Exception
|
||||
{
|
||||
try (IBlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
|
@ -376,6 +377,7 @@ public class WebSocketCloseTest
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
private void dropConnection() throws Exception
|
||||
{
|
||||
try (IBlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
|
|
|
@ -41,8 +41,6 @@ import org.eclipse.jetty.webapp.WebAppContext;
|
|||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
@ -59,212 +57,158 @@ public class WebSocketUpgradeFilterTest
|
|||
@Parameterized.Parameters(name = "{0}")
|
||||
public static List<Object[]> data()
|
||||
{
|
||||
/**
|
||||
* Case A:
|
||||
* 1. embedded-jetty WSUF.configureContext() / app-ws configured at ...
|
||||
* a. during server construction / before server.start (might not be possible with current impl, native SCI not run (yet))
|
||||
* might require NativeSCI.getDefaultFrom() first
|
||||
* b. during server construction / after server.start
|
||||
* c. during server start / via CustomServlet.init()
|
||||
* 2. embedded-jetty WSUF addFilter / app-ws configured at server construction (before server.start)
|
||||
* Case B:
|
||||
* 1. web.xml WSUF / app-ws configured in CustomServlet.init() load-on-start
|
||||
* Case C:
|
||||
* 1. embedded-jetty WSUF.configureContext() / app-ws configured via ServletContextListener.contextInitialized
|
||||
* 2. embedded-jetty WSUF addFilter / app-ws configured via ServletContextListener.contextInitialized
|
||||
* Case D:
|
||||
* 1. web.xml WSUF / app-ws configured via ServletContextListener.contextInitialized
|
||||
*
|
||||
* Every "app-ws configured" means it should access/set ws policy and add ws mappings
|
||||
*/
|
||||
|
||||
final WebSocketCreator infoCreator = new WebSocketCreator()
|
||||
{
|
||||
@Override
|
||||
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
|
||||
{
|
||||
return new InfoSocket();
|
||||
}
|
||||
};
|
||||
final WebSocketCreator infoCreator = (req, resp) -> new InfoSocket();
|
||||
|
||||
List<Object[]> cases = new ArrayList<>();
|
||||
|
||||
// Embedded WSUF.configureContext(), directly app-ws configuration
|
||||
|
||||
cases.add(new Object[]{"wsuf.configureContext/Direct configure", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf.configureContext/Direct configure", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(0);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
|
||||
|
||||
// direct configuration via WSUF
|
||||
wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
Server server1 = new Server();
|
||||
ServerConnector connector = new ServerConnector(server1);
|
||||
connector.setPort(0);
|
||||
server1.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server1.setHandler(context);
|
||||
|
||||
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
|
||||
|
||||
// direct configuration via WSUF
|
||||
wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
|
||||
server1.start();
|
||||
return server1;
|
||||
}});
|
||||
|
||||
// Embedded WSUF.configureContext(), apply app-ws configuration via attribute
|
||||
|
||||
cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(0);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
WebSocketUpgradeFilter.configureContext(context);
|
||||
|
||||
// configuration via attribute
|
||||
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
|
||||
assertThat("NativeWebSocketConfiguration", configuration, notNullValue());
|
||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
|
||||
server.start();
|
||||
|
||||
return server;
|
||||
}
|
||||
Server server12 = new Server();
|
||||
ServerConnector connector = new ServerConnector(server12);
|
||||
connector.setPort(0);
|
||||
server12.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server12.setHandler(context);
|
||||
|
||||
WebSocketUpgradeFilter.configureContext(context);
|
||||
|
||||
// configuration via attribute
|
||||
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
|
||||
assertThat("NativeWebSocketConfiguration", configuration, notNullValue());
|
||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
|
||||
server12.start();
|
||||
|
||||
return server12;
|
||||
}});
|
||||
|
||||
// Embedded WSUF, added as filter, apply app-ws configuration via attribute
|
||||
|
||||
cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(0);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext());
|
||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
|
||||
|
||||
server.start();
|
||||
|
||||
return server;
|
||||
}
|
||||
Server server13 = new Server();
|
||||
ServerConnector connector = new ServerConnector(server13);
|
||||
connector.setPort(0);
|
||||
server13.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server13.setHandler(context);
|
||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext());
|
||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||
context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
|
||||
|
||||
server13.start();
|
||||
|
||||
return server13;
|
||||
}});
|
||||
|
||||
// Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener
|
||||
|
||||
cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(0);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
context.addEventListener(new InfoContextListener());
|
||||
|
||||
server.start();
|
||||
|
||||
return server;
|
||||
}
|
||||
Server server14 = new Server();
|
||||
ServerConnector connector = new ServerConnector(server14);
|
||||
connector.setPort(0);
|
||||
server14.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server14.setHandler(context);
|
||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
context.addEventListener(new InfoContextListener());
|
||||
|
||||
server14.start();
|
||||
|
||||
return server14;
|
||||
}});
|
||||
|
||||
// WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener
|
||||
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server = new WSServer(testDir, "/");
|
||||
|
||||
server.copyWebInf("wsuf-config-via-listener.xml");
|
||||
server.copyClass(InfoSocket.class);
|
||||
server.copyClass(InfoContextAttributeListener.class);
|
||||
server.start();
|
||||
|
||||
WebAppContext webapp = server.createWebAppContext();
|
||||
server.deployWebapp(webapp);
|
||||
|
||||
return server.getServer();
|
||||
}
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server15 = new WSServer(testDir, "/");
|
||||
|
||||
server15.copyWebInf("wsuf-config-via-listener.xml");
|
||||
server15.copyClass(InfoSocket.class);
|
||||
server15.copyClass(InfoContextAttributeListener.class);
|
||||
server15.start();
|
||||
|
||||
WebAppContext webapp = server15.createWebAppContext();
|
||||
server15.deployWebapp(webapp);
|
||||
|
||||
return server15.getServer();
|
||||
}});
|
||||
|
||||
// WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init
|
||||
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server = new WSServer(testDir, "/");
|
||||
|
||||
server.copyWebInf("wsuf-config-via-servlet-init.xml");
|
||||
server.copyClass(InfoSocket.class);
|
||||
server.copyClass(InfoServlet.class);
|
||||
server.start();
|
||||
|
||||
WebAppContext webapp = server.createWebAppContext();
|
||||
server.deployWebapp(webapp);
|
||||
|
||||
return server.getServer();
|
||||
}
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server16 = new WSServer(testDir, "/");
|
||||
|
||||
server16.copyWebInf("wsuf-config-via-servlet-init.xml");
|
||||
server16.copyClass(InfoSocket.class);
|
||||
server16.copyClass(InfoServlet.class);
|
||||
server16.start();
|
||||
|
||||
WebAppContext webapp = server16.createWebAppContext();
|
||||
server16.deployWebapp(webapp);
|
||||
|
||||
return server16.getServer();
|
||||
}});
|
||||
|
||||
// xml based, wsuf, on alternate url-pattern and config attribute location
|
||||
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", new ServerProvider()
|
||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", (ServerProvider) () ->
|
||||
{
|
||||
@Override
|
||||
public Server newServer() throws Exception
|
||||
{
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server = new WSServer(testDir, "/");
|
||||
|
||||
server.copyWebInf("wsuf-alt-config-via-listener.xml");
|
||||
server.copyClass(InfoSocket.class);
|
||||
server.copyClass(InfoContextAltAttributeListener.class);
|
||||
server.start();
|
||||
|
||||
WebAppContext webapp = server.createWebAppContext();
|
||||
server.deployWebapp(webapp);
|
||||
|
||||
return server.getServer();
|
||||
}
|
||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||
|
||||
WSServer server17 = new WSServer(testDir, "/");
|
||||
|
||||
server17.copyWebInf("wsuf-alt-config-via-listener.xml");
|
||||
server17.copyClass(InfoSocket.class);
|
||||
server17.copyClass(InfoContextAltAttributeListener.class);
|
||||
server17.start();
|
||||
|
||||
WebAppContext webapp = server17.createWebAppContext();
|
||||
server17.deployWebapp(webapp);
|
||||
|
||||
return server17.getServer();
|
||||
}});
|
||||
|
||||
return cases;
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.servlet;
|
|||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
@ -37,75 +38,127 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.WSURI;
|
||||
|
||||
/**
|
||||
* Servlet specific {@link UpgradeRequest} implementation.
|
||||
*/
|
||||
public class ServletUpgradeRequest extends UpgradeRequest
|
||||
public class ServletUpgradeRequest implements UpgradeRequest
|
||||
{
|
||||
private static final String CANNOT_MODIFY_SERVLET_REQUEST = "Cannot modify Servlet Request";
|
||||
private final URI requestURI;
|
||||
private final UpgradeHttpServletRequest request;
|
||||
private final boolean secure;
|
||||
private List<HttpCookie> cookies;
|
||||
private Map<String, List<String>> parameterMap;
|
||||
private List<String> subprotocols;
|
||||
|
||||
public ServletUpgradeRequest(HttpServletRequest httpRequest) throws URISyntaxException
|
||||
{
|
||||
super(WSURI.toWebsocket(httpRequest.getRequestURL(), httpRequest.getQueryString()));
|
||||
URI servletURI = URI.create(httpRequest.getRequestURL().toString());
|
||||
this.secure = httpRequest.isSecure();
|
||||
String scheme = secure ? "wss" : "ws";
|
||||
String authority = servletURI.getAuthority();
|
||||
String path = servletURI.getPath();
|
||||
String query = httpRequest.getQueryString();
|
||||
String fragment = null;
|
||||
this.requestURI = new URI(scheme,authority,path,query,fragment);
|
||||
this.request = new UpgradeHttpServletRequest(httpRequest);
|
||||
|
||||
// Parse protocols.
|
||||
Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol");
|
||||
if (requestProtocols != null)
|
||||
{
|
||||
List<String> protocols = new ArrayList<>(2);
|
||||
while (requestProtocols.hasMoreElements())
|
||||
{
|
||||
String candidate = requestProtocols.nextElement();
|
||||
Collections.addAll(protocols, parseProtocols(candidate));
|
||||
}
|
||||
setSubProtocols(protocols);
|
||||
}
|
||||
|
||||
// Parse extensions.
|
||||
Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
|
||||
setExtensions(ExtensionConfig.parseEnum(e));
|
||||
|
||||
// Copy cookies.
|
||||
Cookie[] requestCookies = request.getCookies();
|
||||
if (requestCookies != null)
|
||||
{
|
||||
List<HttpCookie> cookies = new ArrayList<>();
|
||||
for (Cookie requestCookie : requestCookies)
|
||||
{
|
||||
HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue());
|
||||
// No point handling domain/path/expires/secure/httponly on client request cookies
|
||||
cookies.add(cookie);
|
||||
}
|
||||
setCookies(cookies);
|
||||
}
|
||||
|
||||
setHeaders(request.getHeaders());
|
||||
|
||||
// Copy parameters.
|
||||
Map<String, String[]> requestParams = request.getParameterMap();
|
||||
if (requestParams != null)
|
||||
{
|
||||
Map<String, List<String>> params = new HashMap<>(requestParams.size());
|
||||
for (Map.Entry<String, String[]> entry : requestParams.entrySet())
|
||||
params.put(entry.getKey(), Arrays.asList(entry.getValue()));
|
||||
setParameterMap(params);
|
||||
}
|
||||
|
||||
setSession(request.getSession(false));
|
||||
|
||||
setHttpVersion(request.getProtocol());
|
||||
setMethod(request.getMethod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(ExtensionConfig... configs)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtensions(String... configs)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaders()
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
public void complete()
|
||||
{
|
||||
request.complete();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public X509Certificate[] getCertificates()
|
||||
{
|
||||
return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
if(cookies == null)
|
||||
{
|
||||
Cookie[] requestCookies = request.getCookies();
|
||||
if (requestCookies != null)
|
||||
{
|
||||
cookies = new ArrayList<>();
|
||||
for (Cookie requestCookie : requestCookies)
|
||||
{
|
||||
HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue());
|
||||
// No point handling domain/path/expires/secure/httponly on client request cookies
|
||||
cookies.add(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
|
||||
return ExtensionConfig.parseEnum(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
return request.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeaderInt(String name)
|
||||
{
|
||||
String val = request.getHeader(name);
|
||||
if (val == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return Integer.parseInt(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return request.getHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return request.getHeaders().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
return requestURI.getHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying HttpServletRequest that existed at Upgrade time.
|
||||
* <p>
|
||||
|
@ -119,6 +172,12 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpVersion()
|
||||
{
|
||||
return request.getProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocalAddr()}
|
||||
*
|
||||
|
@ -129,6 +188,26 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return request.getLocalAddr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocale()}
|
||||
*
|
||||
* @return the preferred <code>Locale</code> for the client
|
||||
*/
|
||||
public Locale getLocale()
|
||||
{
|
||||
return request.getLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocales()}
|
||||
*
|
||||
* @return an Enumeration of preferred Locale objects
|
||||
*/
|
||||
public Enumeration<Locale> getLocales()
|
||||
{
|
||||
return request.getLocales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocalName()}
|
||||
*
|
||||
|
@ -161,26 +240,34 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return new InetSocketAddress(getLocalAddress(), getLocalPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocale()}
|
||||
*
|
||||
* @return the preferred <code>Locale</code> for the client
|
||||
*/
|
||||
public Locale getLocale()
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return request.getLocale();
|
||||
return request.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocales()}
|
||||
*
|
||||
* @return an Enumeration of preferred Locale objects
|
||||
*/
|
||||
public Enumeration<Locale> getLocales()
|
||||
@Override
|
||||
public String getOrigin()
|
||||
{
|
||||
return request.getLocales();
|
||||
return getHeader("Origin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getParameterMap()
|
||||
{
|
||||
if (parameterMap == null)
|
||||
{
|
||||
Map<String, String[]> requestParams = request.getParameterMap();
|
||||
if (requestParams != null)
|
||||
{
|
||||
parameterMap = new HashMap<>(requestParams.size());
|
||||
for (Map.Entry<String, String[]> entry : requestParams.entrySet())
|
||||
parameterMap.put(entry.getKey(),Arrays.asList(entry.getValue()));
|
||||
}
|
||||
}
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the principal
|
||||
* @deprecated use {@link #getUserPrincipal()} instead
|
||||
|
@ -191,12 +278,21 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return getUserPrincipal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
|
||||
*/
|
||||
public Principal getUserPrincipal()
|
||||
@Override
|
||||
public String getProtocolVersion()
|
||||
{
|
||||
return request.getUserPrincipal();
|
||||
String version = request.getHeader(WebSocketConstants.SEC_WEBSOCKET_VERSION);
|
||||
if(version == null)
|
||||
{
|
||||
return Integer.toString(WebSocketConstants.SPEC_VERSION);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
return requestURI.getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,11 +337,32 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return new InetSocketAddress(getRemoteAddress(), getRemotePort());
|
||||
}
|
||||
|
||||
public String getRequestPath()
|
||||
{
|
||||
// Since this can be called from a filter, we need to be smart about determining the target request path.
|
||||
String contextPath = request.getContextPath();
|
||||
String requestPath = request.getRequestURI();
|
||||
if (requestPath.startsWith(contextPath))
|
||||
requestPath = requestPath.substring(contextPath.length());
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI()
|
||||
{
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
public Object getServletAttribute(String name)
|
||||
{
|
||||
return request.getAttribute(name);
|
||||
}
|
||||
|
||||
public Map<String, Object> getServletAttributes()
|
||||
{
|
||||
return request.getAttributes();
|
||||
}
|
||||
|
||||
|
||||
public Map<String, List<String>> getServletParameters()
|
||||
{
|
||||
return getParameterMap();
|
||||
|
@ -263,14 +380,56 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return request.getSession(false);
|
||||
}
|
||||
|
||||
public void setServletAttribute(String name, Object value)
|
||||
@Override
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
request.setAttribute(name, value);
|
||||
if (subprotocols == null)
|
||||
{
|
||||
Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol");
|
||||
if (requestProtocols != null)
|
||||
{
|
||||
subprotocols = new ArrayList<>(2);
|
||||
while (requestProtocols.hasMoreElements())
|
||||
{
|
||||
String candidate = requestProtocols.nextElement();
|
||||
Collections.addAll(subprotocols,parseProtocols(candidate));
|
||||
}
|
||||
}
|
||||
}
|
||||
return subprotocols;
|
||||
}
|
||||
|
||||
public Object getServletAttribute(String name)
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
|
||||
*/
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
return request.getAttribute(name);
|
||||
return request.getUserPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubProtocol(String test)
|
||||
{
|
||||
for (String protocol : getSubProtocols())
|
||||
{
|
||||
if (protocol.equalsIgnoreCase(test))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOrigin(String test)
|
||||
{
|
||||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
public boolean isUserInRole(String role)
|
||||
|
@ -278,16 +437,6 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return request.isUserInRole(role);
|
||||
}
|
||||
|
||||
public String getRequestPath()
|
||||
{
|
||||
// Since this can be called from a filter, we need to be smart about determining the target request path.
|
||||
String contextPath = request.getContextPath();
|
||||
String requestPath = request.getRequestURI();
|
||||
if (requestPath.startsWith(contextPath))
|
||||
requestPath = requestPath.substring(contextPath.length());
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
private String[] parseProtocols(String protocol)
|
||||
{
|
||||
if (protocol == null)
|
||||
|
@ -298,8 +447,74 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
|||
return protocol.split("\\s*,\\s*");
|
||||
}
|
||||
|
||||
public void complete()
|
||||
@Override
|
||||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
request.complete();
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, List<String> values)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaders(Map<String, List<String>> headers)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHttpVersion(String httpVersion)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMethod(String method)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestURI(URI uri)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
public void setServletAttribute(String name, Object value)
|
||||
{
|
||||
request.setAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSession(Object session)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(List<String> subProtocols)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubProtocols(String... protocols)
|
||||
{
|
||||
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,45 +19,112 @@
|
|||
package org.eclipse.jetty.websocket.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||
|
||||
/**
|
||||
* Servlet Specific UpgradeResponse implementation.
|
||||
*/
|
||||
public class ServletUpgradeResponse extends UpgradeResponse
|
||||
public class ServletUpgradeResponse implements UpgradeResponse
|
||||
{
|
||||
private HttpServletResponse response;
|
||||
private boolean extensionsNegotiated = false;
|
||||
private boolean subprotocolNegotiated = false;
|
||||
|
||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private List<ExtensionConfig> extensions = new ArrayList<>();
|
||||
private boolean success = false;
|
||||
|
||||
public ServletUpgradeResponse(HttpServletResponse response)
|
||||
{
|
||||
this.response = response;
|
||||
|
||||
for (String name : response.getHeaderNames())
|
||||
{
|
||||
headers.put(name, new ArrayList<String>(response.getHeaders(name)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value)
|
||||
{
|
||||
this.response.addHeader(name, value);
|
||||
}
|
||||
|
||||
private void commitHeaders()
|
||||
{
|
||||
// Transfer all headers to the real HTTP response
|
||||
for (Map.Entry<String, List<String>> entry : getHeaders().entrySet())
|
||||
{
|
||||
for (String value : entry.getValue())
|
||||
{
|
||||
response.addHeader(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void complete()
|
||||
{
|
||||
commitHeaders();
|
||||
response = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAcceptedSubProtocol()
|
||||
{
|
||||
return getHeader(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name)
|
||||
{
|
||||
return response.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getHeaderNames()
|
||||
{
|
||||
return getHeaders().keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return getHeaders().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatusCode()
|
||||
{
|
||||
return response.getStatus();
|
||||
}
|
||||
|
||||
public void setStatus(int status)
|
||||
{
|
||||
response.setStatus(status);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getStatusReason()
|
||||
{
|
||||
throw new UnsupportedOperationException("Server cannot get Status Reason Message");
|
||||
throw new UnsupportedOperationException("Servlet's do not support Status Reason");
|
||||
}
|
||||
|
||||
|
||||
public boolean isCommitted()
|
||||
{
|
||||
if (response != null)
|
||||
|
@ -67,17 +134,23 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
|||
// True in all other cases
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isExtensionsNegotiated()
|
||||
{
|
||||
return extensionsNegotiated;
|
||||
}
|
||||
|
||||
|
||||
public boolean isSubprotocolNegotiated()
|
||||
{
|
||||
return subprotocolNegotiated;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSuccess()
|
||||
{
|
||||
return success;
|
||||
}
|
||||
|
||||
public void sendError(int statusCode, String message) throws IOException
|
||||
{
|
||||
setSuccess(false);
|
||||
|
@ -86,7 +159,7 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
|||
response.flushBuffer(); // commit response
|
||||
response = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendForbidden(String message) throws IOException
|
||||
{
|
||||
|
@ -96,27 +169,30 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
|||
response.flushBuffer(); // commit response
|
||||
response = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setAcceptedSubProtocol(String protocol)
|
||||
{
|
||||
super.setAcceptedSubProtocol(protocol);
|
||||
response.setHeader(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||
subprotocolNegotiated = true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setExtensions(List<ExtensionConfig> extensions)
|
||||
public void setExtensions(List<ExtensionConfig> configs)
|
||||
{
|
||||
super.setExtensions(extensions);
|
||||
this.extensions.clear();
|
||||
this.extensions.addAll(configs);
|
||||
String value = ExtensionConfig.toHeaderValue(configs);
|
||||
response.setHeader(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS, value);
|
||||
extensionsNegotiated = true;
|
||||
}
|
||||
|
||||
public void complete()
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
applyHeaders();
|
||||
response = null;
|
||||
response.setHeader(name, value);
|
||||
}
|
||||
|
||||
|
||||
private void applyHeaders()
|
||||
{
|
||||
// Transfer all headers to the real HTTP response
|
||||
|
@ -128,4 +204,27 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatus(int status)
|
||||
{
|
||||
response.setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusCode(int statusCode)
|
||||
{
|
||||
response.setStatus(statusCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusReason(String statusReason)
|
||||
{
|
||||
throw new UnsupportedOperationException("Servlet's do not support Status Reason");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuccess(boolean success)
|
||||
{
|
||||
this.success = success;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue