Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Greg Wilkins 2016-12-02 13:35:16 +11:00
commit 4046ef26d3
64 changed files with 3333 additions and 2284 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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<>();

View File

@ -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)

View File

@ -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()));
}
}

View File

@ -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());

View File

@ -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 &lt;&lt;&lt; and &gt;&gt;&gt;)
* @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};

View File

@ -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));

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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; }
}

View File

@ -38,6 +38,7 @@ public class AnnotatedEchoTest
private static EchoHandler handler;
private static URI serverUri;
@SuppressWarnings("Duplicates")
@BeforeClass
public static void startServer() throws Exception
{

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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));
}

View File

@ -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());
}

View File

@ -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);
}

View File

@ -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.
*/

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 */
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -234,7 +234,7 @@ public abstract class AbstractEventDriver extends AbstractLifeCycle implements I
}
catch (Throwable t)
{
unhandled(t);
this.session.notifyError(t);
throw t;
}
}

View File

@ -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);
}

View File

@ -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()

View File

@ -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);
}
}

View File

@ -422,7 +422,7 @@ public class IOState
public void onOpened()
{
if(LOG.isDebugEnabled())
LOG.debug(" onOpened()");
LOG.debug("onOpened()");
ConnectionState event = null;
synchronized (this)

View File

@ -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 */
}
}

View File

@ -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);
}
}
}
}

View File

@ -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());

View File

@ -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);

View File

@ -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;

View File

@ -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()))

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}