From 6cc0734a1ae356ac1cdc7e0563d7ac4fbe4d4166 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 2 Oct 2012 13:48:51 -0700
Subject: [PATCH 01/34] jetty-9 miscillaneous optimizations: donot dispatch to
HTTP and SPDY; improved executorCallback
---
.../eclipse/jetty/io/AbstractConnection.java | 33 +++---
.../eclipse/jetty/io/SslConnectionTest.java | 31 +++++-
.../eclipse/jetty/server/HttpConnection.java | 104 ++++++++----------
.../org/eclipse/jetty/servlet/Holder.java | 16 +--
.../eclipse/jetty/servlet/ServletHolder.java | 15 ++-
.../servlet/ServletContextHandlerTest.java | 4 +-
.../jetty/spdy/client/SPDYConnection.java | 3 +-
.../java/org/eclipse/jetty/spdy/Promise.java | 82 +-------------
.../eclipse/jetty/util/ExecutorCallback.java | 34 +++++-
9 files changed, 144 insertions(+), 178 deletions(-)
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index ec88f38c0ad..2d6a42b54bd 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -46,21 +46,30 @@ public abstract class AbstractConnection implements Connection
private final EndPoint _endPoint;
private final Executor _executor;
private final Callback _readCallback;
- private int _inputBufferSize=8192;
+ private int _inputBufferSize=2048;
public AbstractConnection(EndPoint endp, Executor executor)
{
- this(endp, executor, true);
+ this(endp,executor,true);
}
-
- public AbstractConnection(EndPoint endp, Executor executor, final boolean dispatchCompletion)
+
+ public AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
{
if (executor == null)
throw new IllegalArgumentException("Executor must not be null!");
_endPoint = endp;
_executor = executor;
- _readCallback = new ExecutorCallback(executor)
+ _readCallback = new ExecutorCallback(executor,0)
{
+ @Override
+ public void completed(Void context)
+ {
+ if (executeOnfillable)
+ super.completed(context);
+ else
+ onCompleted(context);
+ }
+
@Override
protected void onCompleted(Void context)
{
@@ -107,16 +116,10 @@ public abstract class AbstractConnection implements Connection
onFillInterestedFailed(x);
}
- @Override
- protected boolean alwaysDispatchCompletion()
- {
- return dispatchCompletion;
- }
-
@Override
public String toString()
{
- return String.format("AC.ReadCB@%x", AbstractConnection.this.hashCode());
+ return String.format("AC.ExReadCB@%x", AbstractConnection.this.hashCode());
}
};
}
@@ -137,11 +140,11 @@ public abstract class AbstractConnection implements Connection
_inputBufferSize = inputBufferSize;
}
- public Executor getExecutor()
+ protected Executor getExecutor()
{
return _executor;
}
-
+
/**
*
Utility method to be called to register read interest.
*
After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
@@ -186,7 +189,7 @@ public abstract class AbstractConnection implements Connection
*
Callback method invoked when the endpoint failed to be ready to be read.
* @param cause the exception that caused the failure
*/
- public void onFillInterestedFailed(Throwable cause)
+ protected void onFillInterestedFailed(Throwable cause)
{
LOG.debug("{} onFillInterestedFailed {}", this, cause);
if (_endPoint.isOpen())
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
index c3aa1e984e7..1294d7e24d9 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -29,6 +29,8 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@@ -57,7 +59,18 @@ public class SslConnectionTest
private volatile boolean _testFill=true;
private volatile FutureCallback _writeCallback;
protected ServerSocketChannel _connector;
- protected QueuedThreadPool _threadPool = new QueuedThreadPool();
+ final AtomicInteger _dispatches = new AtomicInteger();
+ protected QueuedThreadPool _threadPool = new QueuedThreadPool()
+ {
+
+ @Override
+ public boolean dispatch(Runnable job)
+ {
+ _dispatches.incrementAndGet();
+ return super.dispatch(job);
+ }
+
+ };
protected Scheduler _scheduler = new TimerScheduler();
protected SelectorManager _manager = new SelectorManager()
{
@@ -113,6 +126,7 @@ public class SslConnectionTest
_threadPool.start();
_scheduler.start();
_manager.start();
+
}
@After
@@ -132,7 +146,7 @@ public class SslConnectionTest
public TestConnection(EndPoint endp)
{
- super(endp, _threadPool);
+ super(endp, _threadPool,false);
}
@Override
@@ -228,12 +242,19 @@ public class SslConnectionTest
server.configureBlocking(false);
_manager.accept(server);
- client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
+ client.getOutputStream().write("Hello".getBytes("UTF-8"));
byte[] buffer = new byte[1024];
int len=client.getInputStream().read(buffer);
- Assert.assertEquals(10, len);
- Assert.assertEquals("HelloWorld",new String(buffer,0,len,StringUtil.__UTF8_CHARSET));
+ Assert.assertEquals(5, len);
+ Assert.assertEquals("Hello",new String(buffer,0,len,StringUtil.__UTF8_CHARSET));
+ _dispatches.set(0);
+ client.getOutputStream().write("World".getBytes("UTF-8"));
+ len=5;
+ while(len>0)
+ len-=client.getInputStream().read(buffer);
+ Assert.assertEquals(1, _dispatches.get());
+
client.close();
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 1dad16ce774..80576ab7fb6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -75,7 +75,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public HttpConnection(HttpChannelConfig config, Connector connector, EndPoint endPoint)
{
- super(endPoint, connector.getExecutor());
+ // Tell AbstractConnector executeOnFillable==false because we are guaranteeing that onfillable
+ // will never block nor take an excessive amount of CPU. ie it is OK for the selector thread to
+ // be used. In this case the thread that calls onfillable will be asked to do some IO and parsing.
+ super(endPoint, connector.getExecutor(),false);
_config = config;
_connector = connector;
@@ -193,10 +196,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
while (true)
{
// Can the parser progress (even with an empty buffer)
- boolean event=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
+ boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
// If there is a request buffer, we are re-entering here
- if (!event && BufferUtil.isEmpty(_requestBuffer))
+ if (!call_channel && BufferUtil.isEmpty(_requestBuffer))
{
if (_requestBuffer == null)
_requestBuffer = _bufferPool.acquire(getInputBufferSize(), false);
@@ -232,11 +235,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
// Parse what we have read
- event=_parser.parseNext(_requestBuffer);
+ call_channel=_parser.parseNext(_requestBuffer);
}
// Parse the buffer
- if (event)
+ if (call_channel)
{
// Parse as much content as there is available before calling the channel
// this is both efficient (may queue many chunks), will correctly set available for 100 continues
@@ -250,25 +253,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
- _channel.run();
-
- // Return if the channel is still processing the request
- if (_channel.getState().isSuspending())
- {
- // release buffer if no input being held.
- // This is needed here to handle the case of no request input. If there
- // is request input, then the release is handled by Input@onAllContentConsumed()
- if (_channel.getRequest().getHttpInput().available()==0)
- releaseRequestBuffer();
- return;
- }
-
- // return if the connection has been changed
- if (getEndPoint().getConnection()!=this)
- {
- releaseRequestBuffer();
- return;
- }
+ getExecutor().execute(_channel);
+ return;
}
}
}
@@ -457,49 +443,45 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
reset();
- // Is this thread dispatched from a resume ?
- if (getCurrentConnection() != HttpConnection.this)
+ if (_parser.isStart())
{
- if (_parser.isStart())
+ // it wants to eat more
+ if (_requestBuffer == null)
{
- // it wants to eat more
- if (_requestBuffer == null)
- {
- fillInterested();
- }
- else if (getConnector().isStarted())
- {
- LOG.debug("{} pipelined", this);
+ fillInterested();
+ }
+ else if (getConnector().isStarted())
+ {
+ LOG.debug("{} pipelined", this);
- try
- {
- getExecutor().execute(this);
- }
- catch (RejectedExecutionException e)
- {
- if (getConnector().isStarted())
- LOG.warn(e);
- else
- LOG.ignore(e);
- getEndPoint().close();
- }
- }
- else
+ try
{
+ getExecutor().execute(this);
+ }
+ catch (RejectedExecutionException e)
+ {
+ if (getConnector().isStarted())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
getEndPoint().close();
}
}
-
- if (_parser.isClosed() && !getEndPoint().isOutputShutdown())
+ else
{
- // TODO This is a catch all indicating some protocol handling failure
- // Currently needed for requests saying they are HTTP/2.0.
- // This should be removed once better error handling is in place
- LOG.warn("Endpoint output not shutdown when seeking EOF");
- getEndPoint().shutdownOutput();
+ getEndPoint().close();
}
}
+ if (_parser.isClosed() && !getEndPoint().isOutputShutdown())
+ {
+ // TODO This is a catch all indicating some protocol handling failure
+ // Currently needed for requests saying they are HTTP/2.0.
+ // This should be removed once better error handling is in place
+ LOG.warn("Endpoint output not shutdown when seeking EOF");
+ getEndPoint().shutdownOutput();
+ }
+
// make sure that an oshut connection is driven towards close
// TODO this is a little ugly
if (getEndPoint().isOpen() && getEndPoint().isOutputShutdown())
@@ -537,7 +519,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// Do we have content ready to parse?
if (BufferUtil.isEmpty(_requestBuffer))
- {
+ {
// If no more input
if (getEndPoint().isInputShutdown())
{
@@ -553,7 +535,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// We will need a buffer to read into
if (_requestBuffer==null)
- _requestBuffer=_bufferPool.acquire(getInputBufferSize(),false);
+ {
+ long content_length=_channel.getRequest().getContentLength();
+ int size=getInputBufferSize();
+ if (size extends AbstractLifeCycle implements Dumpable
return _asyncSupported;
}
- /* ------------------------------------------------------------ */
- @Override
- public String toString()
- {
- return _name;
- }
-
/* ------------------------------------------------------------ */
protected void illegalStateIfContextStarted()
{
@@ -282,7 +275,7 @@ public class Holder extends AbstractLifeCycle implements Dumpable
@Override
public void dump(Appendable out, String indent) throws IOException
{
- out.append(_name).append("==").append(_className)
+ out.append(toString())
.append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
}
@@ -294,6 +287,13 @@ public class Holder extends AbstractLifeCycle implements Dumpable
return ContainerLifeCycle.dump(this);
}
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x==%s",_name,hashCode(),_className);
+ }
+
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 99378b09a76..4d0d206c6bf 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -178,15 +178,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
*/
public void setInitOrder(int order)
{
- _initOnStartup=true;
+ _initOnStartup=order>0;
_initOrder = order;
}
- public boolean isSetInitOrder()
- {
- return _initOnStartup;
- }
-
/* ------------------------------------------------------------ */
/** Comparitor by init order.
*/
@@ -931,4 +926,12 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
throw se;
}
}
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
+ }
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
index 61b98c62cef..0f72332cc79 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
@@ -119,8 +119,6 @@ public class ServletContextHandlerTest
assertEquals(2,__testServlets.get());
-
-
assertThat(holder0.getServletInstance(),nullValue());
response =_connector.getResponses("GET /test0 HTTP/1.0\r\n\r\n");
assertThat(response,containsString("200 OK"));
@@ -129,6 +127,8 @@ public class ServletContextHandlerTest
_server.stop();
assertEquals(0,__testServlets.get());
+
+ holder0.setInitOrder(0);
_server.start();
assertEquals(2,__testServlets.get());
assertThat(holder0.getServletInstance(),nullValue());
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
index 9050c3f8cf9..3507b1ac326 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
@@ -52,7 +52,8 @@ public class SPDYConnection extends AbstractConnection implements ControllerA {@link Promise} is a {@link Future} that allows a result or a failure to be set,
- * so that the {@link Future} will be {@link #isDone() done}.
- *
- * @param the type of the result object
- */
-public class Promise implements Callback, Future
+@Deprecated
+public class Promise extends FutureCallback
{
- private final CountDownLatch latch = new CountDownLatch(1);
- private boolean cancelled;
- private Throwable failure;
- private T promise;
-
- @Override
- public void completed(T result)
- {
- this.promise = result;
- latch.countDown();
- }
-
- @Override
- public void failed(T context, Throwable x)
- {
- this.failure = x;
- latch.countDown();
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning)
- {
- cancelled = true;
- latch.countDown();
- return true;
- }
-
- @Override
- public boolean isCancelled()
- {
- return cancelled;
- }
-
- @Override
- public boolean isDone()
- {
- return cancelled || latch.getCount() == 0;
- }
-
- @Override
- public T get() throws InterruptedException, ExecutionException
- {
- latch.await();
- return result();
- }
-
- @Override
- public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
- {
- boolean elapsed = !latch.await(timeout, unit);
- if (elapsed)
- throw new TimeoutException();
- return result();
- }
-
- private T result() throws ExecutionException
- {
- if (isCancelled())
- throw new CancellationException();
- Throwable failure = this.failure;
- if (failure != null)
- throw new ExecutionException(failure);
- return promise;
- }
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java
index be748c0c5f0..69190fe6755 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ExecutorCallback.java
@@ -24,6 +24,14 @@ public abstract class ExecutorCallback implements Callback
{
private final ForkInvoker _invoker;
private final Executor _executor;
+ private final Runnable _onComplete=new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ onCompleted(null);
+ }
+ };
public ExecutorCallback(Executor executor)
{
@@ -33,14 +41,32 @@ public abstract class ExecutorCallback implements Callback
public ExecutorCallback(Executor executor, int maxRecursion)
{
_executor = executor;
- _invoker = new ExecutorCallbackInvoker(maxRecursion);
+ _invoker = maxRecursion>0?new ExecutorCallbackInvoker(maxRecursion):null;
+ if (_executor==null)
+ throw new IllegalArgumentException();
}
@Override
- public final void completed(final C context)
+ public void completed(final C context)
{
// Should we execute?
- if (alwaysDispatchCompletion())
+ if (_invoker==null)
+ {
+ if (context==null)
+ _executor.execute(_onComplete);
+ else
+ {
+ _executor.execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ onCompleted(context);
+ }
+ });
+ }
+ }
+ else if (alwaysDispatchCompletion())
{
_invoker.fork(context);
}
@@ -53,7 +79,7 @@ public abstract class ExecutorCallback implements Callback
protected abstract void onCompleted(C context);
@Override
- public final void failed(final C context, final Throwable x)
+ public void failed(final C context, final Throwable x)
{
// Always execute failure
Runnable runnable = new Runnable()
From 97d08c399ad5497358788ee001cbebeee039d6a7 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 2 Oct 2012 15:00:31 -0700
Subject: [PATCH 02/34] jetty-9 removed race with NPN replacing connection and
extra wrap causing NPE in SSLEngineImpl
---
jetty-io/src/test/resources/jetty-logging.properties | 1 +
.../src/test/resources/jetty-logging.properties | 2 ++
.../jetty/spdy/server/NextProtoNegoServerConnection.java | 9 ++++++---
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/jetty-io/src/test/resources/jetty-logging.properties b/jetty-io/src/test/resources/jetty-logging.properties
index d4922ad1951..1dd07407e8c 100644
--- a/jetty-io/src/test/resources/jetty-logging.properties
+++ b/jetty-io/src/test/resources/jetty-logging.properties
@@ -1,2 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=INFO
+
diff --git a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
index 996b638b001..c8c756e3a78 100644
--- a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
+++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
@@ -1,3 +1,5 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.spdy.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
+#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
index 574515614ed..4bb8fa839ed 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
@@ -35,7 +35,7 @@ import org.eclipse.jetty.util.log.Logger;
public class NextProtoNegoServerConnection extends AbstractConnection implements NextProtoNego.ServerProvider
{
- private final Logger logger = Log.getLogger(getClass());
+ private final Logger LOG = Log.getLogger(getClass());
private final Connector connector;
private final SSLEngine engine;
private final List protocols;
@@ -78,6 +78,9 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
if (filled <= 0 || completed)
break;
}
+
+ if (completed)
+ getEndPoint().getConnection().onOpen();
}
private int fill()
@@ -88,7 +91,7 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
}
catch (IOException x)
{
- logger.debug(x);
+ LOG.debug(x);
getEndPoint().close();
return -1;
}
@@ -109,13 +112,13 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
@Override
public void protocolSelected(String protocol)
{
+ LOG.debug("{} protocolSelected {}",this,protocol);
NextProtoNego.remove(engine);
ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
EndPoint endPoint = getEndPoint();
endPoint.getConnection().onClose();
Connection connection = connectionFactory.newConnection(connector, endPoint);
endPoint.setConnection(connection);
- connection.onOpen();
completed = true;
}
}
From 604f4985d3c7728558e1ee61ce13053345025131 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 2 Oct 2012 15:47:57 -0700
Subject: [PATCH 03/34] jetty 9: Improved NPN client connection to perform the
connection replacement from onFillable() rather than from NPN callback
methods.
---
.../client/NextProtoNegoClientConnection.java | 34 +++++++++--------
.../server/NextProtoNegoServerConnection.java | 38 +++++++++----------
2 files changed, 35 insertions(+), 37 deletions(-)
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
index c3ed5479c82..47e4338b0ed 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NextProtoNegoClientConnection.java
@@ -36,7 +36,7 @@ import org.eclipse.jetty.util.log.Logger;
public class NextProtoNegoClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
{
- private final Logger logger = Log.getLogger(getClass());
+ private final Logger LOG = Log.getLogger(getClass());
private final SocketChannel channel;
private final Object attachment;
private final SPDYClient client;
@@ -49,7 +49,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
this.channel = channel;
this.attachment = attachment;
this.client = client;
- this.engine=endPoint.getSslConnection().getSSLEngine();
+ this.engine = endPoint.getSslConnection().getSSLEngine();
NextProtoNego.put(engine, this);
}
@@ -60,12 +60,12 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
try
{
getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+ fillInterested();
}
catch(IOException e)
{
throw new RuntimeIOException(e);
}
- fillInterested();
}
@Override
@@ -76,9 +76,11 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
int filled = fill();
if (filled == 0 && !completed)
fillInterested();
- if (filled <= 0)
+ if (filled <= 0 || completed)
break;
}
+ if (completed)
+ replaceConnection();
}
private int fill()
@@ -89,7 +91,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
}
catch (IOException x)
{
- logger.debug(x);
+ LOG.debug(x);
getEndPoint().close();
return -1;
}
@@ -105,10 +107,6 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
public void unsupported()
{
NextProtoNego.remove(engine);
- // Server does not support NPN, but this is a SPDY client, so hardcode SPDY
- EndPoint endPoint = getEndPoint();
- Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
- client.replaceConnection(endPoint, connection);
completed = true;
}
@@ -116,14 +114,18 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
public String selectProtocol(List protocols)
{
NextProtoNego.remove(engine);
- String protocol = client.selectProtocol(protocols);
- if (protocol == null)
- return null;
- EndPoint endPoint = getEndPoint();
- Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
- client.replaceConnection(endPoint, connection);
completed = true;
- return protocol;
+ String protocol = client.selectProtocol(protocols);
+ return protocol == null ? null : protocol;
}
+ private void replaceConnection()
+ {
+ EndPoint endPoint = getEndPoint();
+ Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
+ endPoint.getConnection().onClose();
+ endPoint.setConnection(connection);
+ connection.onOpen();
+ completed = true;
+ }
}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
index 4bb8fa839ed..93b3bc15c3b 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NextProtoNegoServerConnection.java
@@ -40,18 +40,16 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
private final SSLEngine engine;
private final List protocols;
private final String defaultProtocol;
- private boolean completed; // No need to be volatile: it is modified and read by the same thread
-
+ private String nextProtocol; // No need to be volatile: it is modified and read by the same thread
public NextProtoNegoServerConnection(DecryptedEndPoint endPoint, Connector connector, Listprotocols, String defaultProtocol)
{
super(endPoint, connector.getExecutor());
this.connector = connector;
this.protocols = protocols;
- this.defaultProtocol=defaultProtocol;
+ this.defaultProtocol = defaultProtocol;
engine = endPoint.getSslConnection().getSSLEngine();
-
- NextProtoNego.put(engine,this);
+ NextProtoNego.put(engine, this);
}
@Override
@@ -61,26 +59,29 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
fillInterested();
}
- @Override
- public void onClose()
- {
- super.onClose();
- }
-
@Override
public void onFillable()
{
while (true)
{
int filled = fill();
- if (filled == 0 && !completed)
+ if (filled == 0 && nextProtocol == null)
fillInterested();
- if (filled <= 0 || completed)
+ if (filled <= 0 || nextProtocol != null)
break;
}
- if (completed)
+ if (nextProtocol != null)
+ {
+ ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
+ EndPoint endPoint = getEndPoint();
+ Connection oldConnection = endPoint.getConnection();
+ oldConnection.onClose();
+ Connection connection = connectionFactory.newConnection(connector, endPoint);
+ LOG.debug("{} switching from {} to {}", this, oldConnection, connection);
+ endPoint.setConnection(connection);
getEndPoint().getConnection().onOpen();
+ }
}
private int fill()
@@ -112,13 +113,8 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
@Override
public void protocolSelected(String protocol)
{
- LOG.debug("{} protocolSelected {}",this,protocol);
+ LOG.debug("{} protocol selected {}", this, protocol);
+ nextProtocol = protocol;
NextProtoNego.remove(engine);
- ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
- EndPoint endPoint = getEndPoint();
- endPoint.getConnection().onClose();
- Connection connection = connectionFactory.newConnection(connector, endPoint);
- endPoint.setConnection(connection);
- completed = true;
}
}
From 435f8a6db896e0bf2697a14c18a1d16bedca7d9b Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 2 Oct 2012 15:49:25 -0700
Subject: [PATCH 04/34] jetty 9: Improved NPN client connection to perform the
connection replacement from onFillable() rather than from NPN callback
methods.
---
.../java/org/eclipse/jetty/spdy/client/SPDYClient.java | 7 -------
1 file changed, 7 deletions(-)
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
index a9374437cbe..9f5804e8d84 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
@@ -153,13 +153,6 @@ public class SPDYClient
return FlowControlStrategyFactory.newFlowControlStrategy(version);
}
- public void replaceConnection(EndPoint endPoint, Connection connection)
- {
- endPoint.getConnection().onClose();
- endPoint.setConnection(connection);
- connection.onOpen();
- }
-
public static class Factory extends ContainerLifeCycle
{
private final Queue sessions = new ConcurrentLinkedQueue<>();
From e1b7b4f101e743528a925552c22599bc67aa37de Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Tue, 2 Oct 2012 18:01:23 -0700
Subject: [PATCH 05/34] jetty-9 fixed broken websocket from prir optimisations
of HTTP dispatch
---
.../eclipse/jetty/server/HttpConnection.java | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 80576ab7fb6..05f1208efa9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -57,6 +57,25 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
private final HttpParser _parser;
private volatile ByteBuffer _requestBuffer = null;
private volatile ByteBuffer _chunk = null;
+
+ // TODO get rid of this
+ private final Runnable _channelRunner = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ setCurrentConnection(HttpConnection.this);
+ _channel.run();
+ }
+ finally
+ {
+ setCurrentConnection(null);
+ }
+
+ }
+ };
public static HttpConnection getCurrentConnection()
{
@@ -253,7 +272,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
- getExecutor().execute(_channel);
+ getExecutor().execute(_channelRunner);
return;
}
}
From b18a5b05dcdb5ac0a927082cc4a3d5c7001bb107 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Wed, 3 Oct 2012 10:12:08 -0700
Subject: [PATCH 06/34] jetty-9 fixed double onFillable after 101 upgrade
---
.../src/main/java/org/eclipse/jetty/server/HttpConnection.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 05f1208efa9..f7b9fd7ee42 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -457,6 +457,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
onClose();
getEndPoint().setConnection(connection);
connection.onOpen();
+ reset();
+ return;
}
}
From d23215f3f4227633bb135de359e586acd1030099 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 4 Oct 2012 10:07:51 -0700
Subject: [PATCH 07/34] Bug 391140 - Implement x-webkit-deflate-frame extension
as-used by Chrome/Safari
---
.../client/internal/io/UpgradeConnection.java | 20 +-
.../client/blockhead/BlockheadServer.java | 16 +-
.../jetty/websocket/core/api/Extension.java | 67 ++++-
.../WebSocketExtensionRegistry.java | 2 +
.../deflate/WebkitDeflateFrameExtension.java | 225 ++++++++++++++
.../permessage/CompressExtension.java | 2 +-
...inaryValidator.java => NoOpValidator.java} | 6 +-
.../websocket/core/protocol/Generator.java | 42 ++-
.../jetty/websocket/core/protocol/Parser.java | 67 +++--
.../websocket/core/extensions/AllTests.java | 2 +-
...java => CompressMessageExtensionTest.java} | 15 +-
.../WebkitDeflateFrameExtensionTest.java | 282 ++++++++++++++++++
.../protocol/OutgoingNetworkBytesCapture.java | 68 +++++
.../websocket/server/WebSocketHandler.java | 2 +
.../server/WebSocketServerFactory.java | 28 +-
.../jetty/websocket/server/ChromeTest.java | 4 +-
.../server/WebSocketInvalidVersionTest.java | 7 +-
.../server/blockhead/BlockheadClient.java | 27 +-
.../server/browser/BrowserDebugTool.java | 125 ++++++++
.../server/browser/BrowserSocket.java | 144 +++++++++
.../resources/browser-debug-tool/index.html | 25 ++
.../resources/browser-debug-tool/main.css | 29 ++
.../resources/browser-debug-tool/websocket.js | 121 ++++++++
.../test/resources/jetty-logging.properties | 4 +
24 files changed, 1188 insertions(+), 142 deletions(-)
create mode 100644 jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/deflate/WebkitDeflateFrameExtension.java
rename jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/{BinaryValidator.java => NoOpValidator.java} (86%)
rename jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/{DeflateFrameExtensionTest.java => CompressMessageExtensionTest.java} (97%)
create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/WebkitDeflateFrameExtensionTest.java
create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java
create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
create mode 100644 jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
create mode 100644 jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css
create mode 100644 jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/io/UpgradeConnection.java
index f8dc160d0be..d80ba67aa30 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/io/UpgradeConnection.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/io/UpgradeConnection.java
@@ -230,6 +230,9 @@ public class UpgradeConnection extends AbstractConnection
// Connect extensions
if (extensions != null)
{
+ connection.getParser().configureFromExtensions(extensions);
+ connection.getGenerator().configureFromExtensions(extensions);
+
Iterator extIter;
// Connect outgoings
extIter = extensions.iterator();
@@ -238,23 +241,6 @@ public class UpgradeConnection extends AbstractConnection
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
-
- // Handle RSV reservations
- if (ext.useRsv1())
- {
- connection.getGenerator().setRsv1InUse(true);
- connection.getParser().setRsv1InUse(true);
- }
- if (ext.useRsv2())
- {
- connection.getGenerator().setRsv2InUse(true);
- connection.getParser().setRsv2InUse(true);
- }
- if (ext.useRsv3())
- {
- connection.getGenerator().setRsv3InUse(true);
- connection.getParser().setRsv3InUse(true);
- }
}
// Connect incomings
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
index 60d33268964..2dacd1a902e 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
@@ -368,6 +368,8 @@ public class BlockheadServer
// Connect extensions
if (!extensions.isEmpty())
{
+ generator.configureFromExtensions(extensions);
+
Iterator extIter;
// Connect outgoings
extIter = extensions.iterator();
@@ -376,20 +378,6 @@ public class BlockheadServer
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
-
- // Handle RSV reservations
- if (ext.useRsv1())
- {
- generator.setRsv1InUse(true);
- }
- if (ext.useRsv2())
- {
- generator.setRsv2InUse(true);
- }
- if (ext.useRsv3())
- {
- generator.setRsv3InUse(true);
- }
}
// Connect incomings
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/api/Extension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/api/Extension.java
index 007de27ecb2..50a2d13301f 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/api/Extension.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/api/Extension.java
@@ -88,6 +88,58 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
nextIncoming(frame);
}
+ /**
+ * Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing.
+ *
+ * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1.
+ *
+ * @return true if extension uses RSV1 for its own purposes.
+ */
+ public boolean isRsv1User()
+ {
+ return false;
+ }
+
+ /**
+ * Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing.
+ *
+ * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2.
+ *
+ * @return true if extension uses RSV2 for its own purposes.
+ */
+ public boolean isRsv2User()
+ {
+ return false;
+ }
+
+ /**
+ * Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing.
+ *
+ * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3.
+ *
+ * @return true if extension uses RSV3 for its own purposes.
+ */
+ public boolean isRsv3User()
+ {
+ return false;
+ }
+
+ /**
+ * Used to indicate that the extension works as a decoder of TEXT Data Frames.
+ *
+ * This is used to adjust validation during parsing/generating, as per spec TEXT Data Frames can only contain UTF8 encoded String data.
+ *
+ * Example: a compression extension will process a compressed set of text data, the parser/generator should no longer be concerned about the validity of the
+ * TEXT Data Frames as this is now the responsibility of the extension.
+ *
+ * @return true if extension will process TEXT Data Frames, false if extension makes no modifications of TEXT Data Frames. If false, the parser/generator is
+ * now free to validate the conformance to spec of TEXT Data Frames.
+ */
+ public boolean isTextDataDecoder()
+ {
+ return false;
+ }
+
/**
* Convenience method for {@link #getNextIncomingFrames()#incoming(WebSocketException)}
*
@@ -173,19 +225,4 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
{
this.policy = policy;
}
-
- public boolean useRsv1()
- {
- return false;
- }
-
- public boolean useRsv2()
- {
- return false;
- }
-
- public boolean useRsv3()
- {
- return false;
- }
}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/WebSocketExtensionRegistry.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/WebSocketExtensionRegistry.java
index ebd5fc5d8e3..15dbe5c5403 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/WebSocketExtensionRegistry.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/WebSocketExtensionRegistry.java
@@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.ExtensionRegistry;
import org.eclipse.jetty.websocket.core.api.WebSocketException;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.core.extensions.deflate.WebkitDeflateFrameExtension;
import org.eclipse.jetty.websocket.core.extensions.fragment.FragmentExtension;
import org.eclipse.jetty.websocket.core.extensions.identity.IdentityExtension;
import org.eclipse.jetty.websocket.core.extensions.permessage.CompressExtension;
@@ -50,6 +51,7 @@ public class WebSocketExtensionRegistry implements ExtensionRegistry
this.registry.put("identity",IdentityExtension.class);
this.registry.put("fragment",FragmentExtension.class);
+ this.registry.put("x-webkit-deflate-frame",WebkitDeflateFrameExtension.class);
this.registry.put("permessage-compress",CompressExtension.class);
}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/deflate/WebkitDeflateFrameExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/deflate/WebkitDeflateFrameExtension.java
new file mode 100644
index 00000000000..0d771616f68
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/deflate/WebkitDeflateFrameExtension.java
@@ -0,0 +1,225 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.core.extensions.deflate;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.api.BadPayloadException;
+import org.eclipse.jetty.websocket.core.api.Extension;
+import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+/**
+ * Implementation of the x-webkit-deflate-frame extension seen out
+ * in the wild.
+ */
+public class WebkitDeflateFrameExtension extends Extension
+{
+ private static final Logger LOG = Log.getLogger(WebkitDeflateFrameExtension.class);
+ private static final int BUFFER_SIZE = 64 * 1024;
+
+ private Deflater deflater;
+ private Inflater inflater;
+
+ public ByteBuffer deflate(ByteBuffer data)
+ {
+ int length = data.remaining();
+
+ // prepare the uncompressed input
+ deflater.reset();
+ deflater.setInput(BufferUtil.toArray(data));
+ deflater.finish();
+
+ // prepare the output buffer
+ int bufsize = Math.max(BUFFER_SIZE,length * 2);
+ ByteBuffer buf = getBufferPool().acquire(bufsize,false);
+ BufferUtil.clearToFill(buf);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Uncompressed length={} - {}",length,buf.position());
+ }
+
+ while (!deflater.finished())
+ {
+ byte out[] = new byte[BUFFER_SIZE];
+ int len = deflater.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Deflater: finished={}, needsInput={}, len={} / input.len={}",deflater.finished(),deflater.needsInput(),len,length);
+ }
+
+ buf.put(out,0,len);
+ }
+ BufferUtil.flipToFlush(buf,0);
+
+ /* Per the spec, it says that BFINAL 1 or 0 are allowed.
+ * However, Java always uses BFINAL 1, where the browsers
+ * Chrome and Safari fail to decompress when it encounters
+ * BFINAL 1.
+ *
+ * This hack will always set BFINAL 0
+ */
+ byte b0 = buf.get(0);
+ if ((b0 & 1) != 0) // if BFINAL 1
+ {
+ buf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
+ }
+ return buf;
+ }
+
+ @Override
+ public void incoming(WebSocketFrame frame)
+ {
+ if (frame.isControlFrame() || !frame.isRsv1())
+ {
+ // Cannot modify incoming control frames or ones with RSV1 set.
+ super.incoming(frame);
+ return;
+ }
+
+ LOG.debug("Decompressing Frame: {}",frame);
+
+ ByteBuffer data = frame.getPayload();
+ try
+ {
+ ByteBuffer uncompressed = inflate(data);
+ frame.setPayload(uncompressed);
+ nextIncoming(frame);
+ }
+ finally
+ {
+ // release original buffer (no longer needed)
+ getBufferPool().release(data);
+ }
+ }
+
+ public ByteBuffer inflate(ByteBuffer data)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("inflate: {}",BufferUtil.toDetailString(data));
+ LOG.debug("raw data: {}",TypeUtil.toHexString(BufferUtil.toArray(data)));
+ }
+
+ // Set the data that is compressed to the inflater
+ byte compressed[] = BufferUtil.toArray(data);
+ inflater.reset();
+ inflater.setInput(compressed,0,compressed.length);
+
+ // Establish place for inflated data
+ byte buf[] = new byte[BUFFER_SIZE];
+ try
+ {
+ int inflated = inflater.inflate(buf);
+ if (inflated == 0)
+ {
+ throw new DataFormatException("Insufficient compressed data");
+ }
+
+ ByteBuffer ret = ByteBuffer.wrap(buf,0,inflated);
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("uncompressed={}",BufferUtil.toDetailString(ret));
+ }
+
+ return ret;
+ }
+ catch (DataFormatException e)
+ {
+ LOG.warn(e);
+ throw new BadPayloadException(e);
+ }
+ }
+
+ /**
+ * Indicates use of RSV1 flag for indicating deflation is in use.
+ *
+ * Also known as the "COMP" framing header bit
+ */
+ @Override
+ public boolean isRsv1User()
+ {
+ return true;
+ }
+
+ /**
+ * Indicate that this extensions is now responsible for TEXT Data Frame compliance to the WebSocket spec.
+ */
+ @Override
+ public boolean isTextDataDecoder()
+ {
+ return true;
+ }
+
+ @Override
+ public void output(C context, Callback callback, WebSocketFrame frame) throws IOException
+ {
+ if (frame.isControlFrame())
+ {
+ // skip, cannot compress control frames.
+ nextOutput(context,callback,frame);
+ return;
+ }
+
+ ByteBuffer data = frame.getPayload();
+ try
+ {
+ // deflate data
+ ByteBuffer buf = deflate(data);
+ frame.setPayload(buf);
+ frame.setRsv1(true);
+ nextOutput(context,callback,frame);
+ }
+ finally
+ {
+ // free original data buffer
+ getBufferPool().release(data);
+ }
+ }
+
+ @Override
+ public void setConfig(ExtensionConfig config)
+ {
+ super.setConfig(config);
+
+ boolean nowrap = true;
+
+ deflater = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
+ deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
+ inflater = new Inflater(nowrap);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("DeflateFrameExtension[]");
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/permessage/CompressExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/permessage/CompressExtension.java
index 51ab6ac0fa0..f7b9c5211c4 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/permessage/CompressExtension.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/permessage/CompressExtension.java
@@ -267,7 +267,7 @@ public class CompressExtension extends Extension
* Indicates use of RSV1 flag for indicating deflation is in use.
*/
@Override
- public boolean useRsv1()
+ public boolean isRsv1User()
{
return true;
}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/BinaryValidator.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/NoOpValidator.java
similarity index 86%
rename from jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/BinaryValidator.java
rename to jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/NoOpValidator.java
index db73271fa61..76ece6d8762 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/BinaryValidator.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/payload/NoOpValidator.java
@@ -23,11 +23,11 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
/**
- * Binary payload validator does nothing, essentially.
+ * payload validator does no validation.
*/
-public class BinaryValidator implements PayloadProcessor
+public class NoOpValidator implements PayloadProcessor
{
- public static final BinaryValidator INSTANCE = new BinaryValidator();
+ public static final NoOpValidator INSTANCE = new NoOpValidator();
@Override
public void process(ByteBuffer payload)
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Generator.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Generator.java
index 167ea2516f9..ed10883fa40 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Generator.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Generator.java
@@ -19,11 +19,13 @@
package org.eclipse.jetty.websocket.core.protocol;
import java.nio.ByteBuffer;
+import java.util.List;
import org.eclipse.jetty.io.ByteBufferPool;
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.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.ProtocolException;
import org.eclipse.jetty.websocket.core.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
@@ -164,6 +166,31 @@ public class Generator
}
+ public void configureFromExtensions(List extends Extension> exts)
+ {
+ // default
+ this.rsv1InUse = false;
+ this.rsv2InUse = false;
+ this.rsv3InUse = false;
+
+ // configure from list of extensions in use
+ for(Extension ext: exts)
+ {
+ if (ext.isRsv1User())
+ {
+ this.rsv1InUse = true;
+ }
+ if (ext.isRsv2User())
+ {
+ this.rsv2InUse = true;
+ }
+ if (ext.isRsv3User())
+ {
+ this.rsv3InUse = true;
+ }
+ }
+ }
+
/**
* Generate, into a ByteBuffer, no more than bufferSize of contents from the frame. If the frame exceeds the bufferSize, then multiple calls to
* {@link #generate(int, WebSocketFrame)} are required to obtain each window of ByteBuffer to complete the frame.
@@ -368,21 +395,6 @@ public class Generator
return rsv3InUse;
}
- public void setRsv1InUse(boolean rsv1InUse)
- {
- this.rsv1InUse = rsv1InUse;
- }
-
- public void setRsv2InUse(boolean rsv2InUse)
- {
- this.rsv2InUse = rsv2InUse;
- }
-
- public void setRsv3InUse(boolean rsv3InUse)
- {
- this.rsv3InUse = rsv3InUse;
- }
-
@Override
public String toString()
{
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Parser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Parser.java
index 0c7fe1d8b98..5f7acc5fe44 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Parser.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/Parser.java
@@ -19,18 +19,20 @@
package org.eclipse.jetty.websocket.core.protocol;
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.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.MessageTooLargeException;
import org.eclipse.jetty.websocket.core.api.ProtocolException;
import org.eclipse.jetty.websocket.core.api.WebSocketException;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.core.io.IncomingFrames;
-import org.eclipse.jetty.websocket.core.io.payload.BinaryValidator;
import org.eclipse.jetty.websocket.core.io.payload.CloseReasonValidator;
import org.eclipse.jetty.websocket.core.io.payload.DeMaskProcessor;
+import org.eclipse.jetty.websocket.core.io.payload.NoOpValidator;
import org.eclipse.jetty.websocket.core.io.payload.PayloadProcessor;
import org.eclipse.jetty.websocket.core.io.payload.UTF8Validator;
@@ -71,6 +73,8 @@ public class Parser
private boolean rsv2InUse = false;
/** Is there an extension using RSV3 */
private boolean rsv3InUse = false;
+ /** Is there an extension that processes invalid UTF8 text messages (such as compressed content) */
+ private boolean isTextFrameValidated = true;
private static final Logger LOG = Log.getLogger(Parser.class);
private IncomingFrames incomingFramesHandler;
@@ -111,6 +115,36 @@ public class Parser
}
}
+ public void configureFromExtensions(List extends Extension> exts)
+ {
+ // default
+ this.rsv1InUse = false;
+ this.rsv2InUse = false;
+ this.rsv3InUse = false;
+ this.isTextFrameValidated = true;
+
+ // configure from list of extensions in use
+ for(Extension ext: exts)
+ {
+ if (ext.isRsv1User())
+ {
+ this.rsv1InUse = true;
+ }
+ if (ext.isRsv2User())
+ {
+ this.rsv2InUse = true;
+ }
+ if (ext.isRsv3User())
+ {
+ this.rsv3InUse = true;
+ }
+ if (ext.isTextDataDecoder())
+ {
+ this.isTextFrameValidated = false;
+ }
+ }
+ }
+
public IncomingFrames getIncomingFramesHandler()
{
return incomingFramesHandler;
@@ -262,7 +296,10 @@ public class Parser
throw new ProtocolException("Unknown opcode: " + opc);
}
- LOG.debug("OpCode {}, fin={}",OpCode.name(opcode),fin);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("OpCode {}, fin={} rsv={}{}{}",OpCode.name(opcode),fin,(rsv1?'1':'.'),(rsv2?'1':'.'),(rsv3?'1':'.'));
+ }
/*
* RFC 6455 Section 5.2
@@ -290,13 +327,20 @@ public class Parser
switch (opcode)
{
case OpCode.TEXT:
- strictnessProcessor = new UTF8Validator();
+ if (isTextFrameValidated)
+ {
+ strictnessProcessor = new UTF8Validator();
+ }
+ else
+ {
+ strictnessProcessor = NoOpValidator.INSTANCE;
+ }
break;
case OpCode.CLOSE:
strictnessProcessor = new CloseReasonValidator();
break;
default:
- strictnessProcessor = BinaryValidator.INSTANCE;
+ strictnessProcessor = NoOpValidator.INSTANCE;
break;
}
@@ -539,21 +583,6 @@ public class Parser
this.incomingFramesHandler = incoming;
}
- public void setRsv1InUse(boolean rsv1InUse)
- {
- this.rsv1InUse = rsv1InUse;
- }
-
- public void setRsv2InUse(boolean rsv2InUse)
- {
- this.rsv2InUse = rsv2InUse;
- }
-
- public void setRsv3InUse(boolean rsv3InUse)
- {
- this.rsv3InUse = rsv3InUse;
- }
-
@Override
public String toString()
{
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AllTests.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AllTests.java
index 8f51066477b..5e2cce18f5c 100644
--- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AllTests.java
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/AllTests.java
@@ -23,7 +23,7 @@ import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(
- { DeflateFrameExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class })
+{ CompressMessageExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class, WebkitDeflateFrameExtensionTest.class })
public class AllTests
{
/* nothing to do here, its all done in the annotations */
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/CompressMessageExtensionTest.java
similarity index 97%
rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/DeflateFrameExtensionTest.java
rename to jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/CompressMessageExtensionTest.java
index 90344dec737..83ceb8224d4 100644
--- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/DeflateFrameExtensionTest.java
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/CompressMessageExtensionTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.core.extensions;
+import static org.hamcrest.Matchers.*;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -36,19 +38,12 @@ import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
import org.eclipse.jetty.websocket.core.protocol.IncomingFramesCapture;
import org.eclipse.jetty.websocket.core.protocol.OpCode;
import org.eclipse.jetty.websocket.core.protocol.OutgoingFramesCapture;
-import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.eclipse.jetty.websocket.core.protocol.OutgoingFramesCapture.Write;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.junit.Assert;
import org.junit.Test;
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
-
-public class DeflateFrameExtensionTest
+public class CompressMessageExtensionTest
{
/**
* Test a large payload (a payload length over 65535 bytes)
@@ -206,7 +201,7 @@ public class DeflateFrameExtensionTest
CompressExtension ext = new CompressExtension();
ext.setBufferPool(new MappedByteBufferPool());
ext.setPolicy(WebSocketPolicy.newServerPolicy());
- ExtensionConfig config = ExtensionConfig.parse("x-deflate-frame;minLength=16");
+ ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
ext.setConfig(config);
ext.setNextIncomingFrames(capture);
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/WebkitDeflateFrameExtensionTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/WebkitDeflateFrameExtensionTest.java
new file mode 100644
index 00000000000..0458f1a2633
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/WebkitDeflateFrameExtensionTest.java
@@ -0,0 +1,282 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.core.extensions;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.core.ByteBufferAssert;
+import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.core.extensions.deflate.WebkitDeflateFrameExtension;
+import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
+import org.eclipse.jetty.websocket.core.protocol.Generator;
+import org.eclipse.jetty.websocket.core.protocol.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.core.protocol.OpCode;
+import org.eclipse.jetty.websocket.core.protocol.OutgoingNetworkBytesCapture;
+import org.eclipse.jetty.websocket.core.protocol.Parser;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebkitDeflateFrameExtensionTest
+{
+ private void assertIncoming(byte[] raw, String... expectedTextDatas)
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+ IncomingFramesCapture capture = new IncomingFramesCapture();
+
+ WebkitDeflateFrameExtension ext = new WebkitDeflateFrameExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+
+ ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
+ ext.setConfig(config);
+
+ ext.setNextIncomingFrames(capture);
+
+ Parser parser = new Parser(policy);
+ parser.configureFromExtensions(Collections.singletonList(ext));
+ parser.setIncomingFramesHandler(ext);
+
+ parser.parse(ByteBuffer.wrap(raw));
+
+ int len = expectedTextDatas.length;
+ capture.assertFrameCount(len);
+ capture.assertHasFrame(OpCode.TEXT,len);
+
+ for (int i = 0; i < len; i++)
+ {
+ WebSocketFrame actual = capture.getFrames().get(i);
+ String prefix = "Frame[" + i + "]";
+ Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
+ Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
+ Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
+ Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+ Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+ ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StringUtil.__UTF8_CHARSET);
+ Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+ ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+ }
+ }
+
+ private void assertOutgoing(String text, String expectedHex) throws IOException
+ {
+ WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+
+ WebkitDeflateFrameExtension ext = new WebkitDeflateFrameExtension();
+ ext.setBufferPool(new MappedByteBufferPool());
+ ext.setPolicy(policy);
+
+ ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
+ ext.setConfig(config);
+
+ ByteBufferPool bufferPool = new MappedByteBufferPool();
+ boolean validating = true;
+ Generator generator = new Generator(policy,bufferPool,validating);
+ generator.configureFromExtensions(Collections.singletonList(ext));
+
+ OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
+ ext.setNextOutgoingFrames(capture);
+
+ WebSocketFrame frame = WebSocketFrame.text(text);
+ ext.output(null,new FutureCallback(),frame);
+
+ capture.assertBytes(0,expectedHex);
+ }
+
+ private String deflate(byte data[], int level, boolean nowrap, int strategy, int flush)
+ {
+ Deflater compressor = new Deflater(level,nowrap);
+ compressor.setStrategy(strategy);
+
+ // Prime the compressor
+ compressor.reset();
+ compressor.setInput(data,0,data.length);
+ compressor.finish();
+
+ byte out[] = new byte[64];
+ int len = compressor.deflate(out,0,out.length,flush);
+ compressor.end();
+
+ String ret = TypeUtil.toHexString(out,0,len);
+ System.out.printf("deflate(l=%d,s=%d,f=%d,w=%-5b) => %s%n",level,strategy,flush,nowrap,ret);
+ return ret;
+ }
+
+ @Test
+ public void testAllDeflate() throws Exception
+ {
+ int strategies[] = new int[] {
+ Deflater.DEFAULT_STRATEGY,
+ Deflater.FILTERED,
+ Deflater.HUFFMAN_ONLY
+ };
+ int flushes[] = new int[] {
+ Deflater.FULL_FLUSH,
+ Deflater.NO_FLUSH,
+ Deflater.SYNC_FLUSH
+ };
+
+ byte uncompressed[] = StringUtil.getUtf8Bytes("info:");
+
+ for(int level = 0; level <= 9; level++)
+ {
+ for (int strategy : strategies)
+ {
+ for (int flush : flushes)
+ {
+ deflate(uncompressed,level,true,strategy,flush);
+ deflate(uncompressed,level,false,strategy,flush);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testChrome20_Hello()
+ {
+ // Captured from Chrome 20.x - "Hello" (sent from browser/client)
+ byte rawbuf[] = TypeUtil.fromHexString("c187832b5c11716391d84a2c5c");
+ assertIncoming(rawbuf,"Hello");
+ }
+
+ @Test
+ public void testChrome20_Info()
+ {
+ // Captured from Chrome 20.x - "info:" (sent from browser/client)
+ byte rawbuf[] = TypeUtil.fromHexString("c187ca4def7f0081a4b47d4fef");
+ assertIncoming(rawbuf,"info:");
+ }
+
+ @Test
+ public void testDeflateBasics() throws Exception
+ {
+ // Setup deflater basics
+ boolean nowrap = true;
+ Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
+ compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+
+ // Text to compress
+ String text = "info:";
+ byte uncompressed[] = StringUtil.getUtf8Bytes(text);
+
+ // Prime the compressor
+ compressor.reset();
+ compressor.setInput(uncompressed,0,uncompressed.length);
+ compressor.finish();
+
+ // Perform compression
+ ByteBuffer outbuf = ByteBuffer.allocate(64);
+ BufferUtil.clearToFill(outbuf);
+
+ while (!compressor.finished())
+ {
+ byte out[] = new byte[64];
+ int len = compressor.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
+ if (len > 0)
+ {
+ System.err.printf("Compressed %,d bytes%n",len);
+ outbuf.put(out,0,len);
+ }
+ }
+ compressor.end();
+
+ BufferUtil.flipToFlush(outbuf,0);
+ byte b0 = outbuf.get(0);
+ if ((b0 & 1) != 0)
+ {
+ outbuf.put(0,(b0 ^= 1));
+ }
+ byte compressed[] = BufferUtil.toArray(outbuf);
+
+ String actual = TypeUtil.toHexString(compressed);
+ String expected = "CaCc4bCbB70200"; // what pywebsocket produces
+ // String expected = "CbCc4bCbB70200"; // what java produces
+
+ System.out.printf("Compressed data: %s%n",actual);
+ Assert.assertThat("Compressed data",actual,is(expected));
+ }
+
+ @Test
+ public void testInflateBasics() throws Exception
+ {
+ // should result in "info:" text if properly inflated
+ byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
+ // byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces
+
+ Inflater inflater = new Inflater(true);
+ inflater.reset();
+ inflater.setInput(rawbuf,0,rawbuf.length);
+
+ byte outbuf[] = new byte[64];
+ int len = inflater.inflate(outbuf);
+ inflater.end();
+ Assert.assertThat("Inflated length",len,greaterThan(4));
+
+ String actual = StringUtil.toUTF8String(outbuf,0,len);
+ Assert.assertThat("Inflated text",actual,is("info:"));
+ }
+
+ @Test
+ public void testPyWebSocketServer_Hello()
+ {
+ // Captured from PyWebSocket - "Hello" (echo from server)
+ byte rawbuf[] = TypeUtil.fromHexString("c107f248cdc9c90700");
+ assertIncoming(rawbuf, "Hello");
+ }
+
+ @Test
+ public void testPyWebSocketServer_Long()
+ {
+ // Captured from PyWebSocket - Long Text (echo from server)
+ byte rawbuf[] = TypeUtil.fromHexString("c1421cca410a80300c44d1abccce9df7" + "f018298634d05631138ab7b7b8fdef1f" + "dc0282e2061d575a45f6f2686bab25e1"
+ + "3fb7296fa02b5885eb3b0379c394f461" + "98cafd03");
+ assertIncoming(rawbuf,"It's a big enough umbrella but it's always me that ends up getting wet.");
+ }
+
+ @Test
+ public void testPyWebSocketServer_Medium()
+ {
+ // Captured from PyWebSocket - "stackoverflow" (echo from server)
+ byte rawbuf[]=TypeUtil.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700");
+ assertIncoming(rawbuf, "stackoverflow");
+ }
+
+ /**
+ * Make sure that the server generated compressed form for "Hello" is
+ * consistent with what PyWebSocket creates.
+ */
+ @Test
+ public void testServerGeneratedHello() throws IOException
+ {
+ assertOutgoing("Hello", "c107f248cdc9c90700");
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java
new file mode 100644
index 00000000000..2ce24d94682
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.core.protocol;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.core.io.OutgoingFrames;
+import org.junit.Assert;
+
+/**
+ * Capture outgoing network bytes.
+ */
+public class OutgoingNetworkBytesCapture implements OutgoingFrames
+{
+ private final Generator generator;
+ private List captured;
+
+ public OutgoingNetworkBytesCapture(Generator generator)
+ {
+ this.generator = generator;
+ this.captured = new ArrayList<>();
+ }
+
+ public void assertBytes(int idx, String expectedHex)
+ {
+ Assert.assertThat("Capture index does not exist",idx,lessThan(captured.size()));
+ ByteBuffer buf = captured.get(idx);
+ String actualHex = TypeUtil.toHexString(BufferUtil.toArray(buf)).toUpperCase();
+ Assert.assertThat("captured[" + idx + "]",actualHex,is(expectedHex.toUpperCase()));
+ }
+
+ public List getCaptured()
+ {
+ return captured;
+ }
+
+ @Override
+ public void output(C context, Callback callback, WebSocketFrame frame) throws IOException
+ {
+ ByteBuffer buf = generator.generate(frame);
+ captured.add(buf.slice());
+ callback.completed(context);
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java
index 728d2942f58..17e0e2f89c0 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java
@@ -58,6 +58,7 @@ public abstract class WebSocketHandler extends HandlerWrapper
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
configurePolicy(policy);
webSocketFactory = new WebSocketServerFactory(policy);
+ addBean(webSocketFactory);
}
public abstract void configure(WebSocketServerFactory factory);
@@ -88,6 +89,7 @@ public abstract class WebSocketHandler extends HandlerWrapper
if (webSocketFactory.acceptWebSocket(request,response))
{
// We have a socket instance created
+ baseRequest.setHandled(true);
return;
}
// If we reach this point, it means we had an incoming request to upgrade
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
index 78c99d128ec..c0ffff98155 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
@@ -56,7 +56,6 @@ import org.eclipse.jetty.websocket.core.io.WebSocketSession;
import org.eclipse.jetty.websocket.core.io.event.EventDriver;
import org.eclipse.jetty.websocket.core.io.event.EventDriverFactory;
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
-import org.eclipse.jetty.websocket.server.handshake.HandshakeHixie76;
import org.eclipse.jetty.websocket.server.handshake.HandshakeRFC6455;
/**
@@ -69,7 +68,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
private final Map handshakes = new HashMap<>();
{
handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
- handshakes.put(HandshakeHixie76.VERSION,new HandshakeHixie76());
+ // OLD!! handshakes.put(HandshakeHixie76.VERSION,new HandshakeHixie76());
}
private final Queue sessions = new ConcurrentLinkedQueue<>();
@@ -364,8 +363,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
Executor executor = http.getConnector().getExecutor();
ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
WebSocketServerConnection connection = new WebSocketServerConnection(endp,executor,scheduler,driver.getPolicy(),bufferPool,this);
- // Tell jetty about the new connection
- request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);
LOG.debug("HttpConnection: {}",http);
LOG.debug("AsyncWebSocketConnection: {}",connection);
@@ -383,6 +380,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// Connect extensions
if (extensions != null)
{
+ connection.getParser().configureFromExtensions(extensions);
+ connection.getGenerator().configureFromExtensions(extensions);
+
Iterator extIter;
// Connect outgoings
extIter = extensions.iterator();
@@ -391,23 +391,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
-
- // Handle RSV reservations
- if (ext.useRsv1())
- {
- connection.getGenerator().setRsv1InUse(true);
- connection.getParser().setRsv1InUse(true);
- }
- if (ext.useRsv2())
- {
- connection.getGenerator().setRsv2InUse(true);
- connection.getParser().setRsv2InUse(true);
- }
- if (ext.useRsv3())
- {
- connection.getGenerator().setRsv3InUse(true);
- connection.getParser().setRsv3InUse(true);
- }
}
// Connect incomings
@@ -426,6 +409,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// configure connection for incoming flows
connection.getParser().setIncomingFramesHandler(incoming);
+ // Tell jetty about the new connection
+ request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);
+
// Process (version specific) handshake response
LOG.debug("Handshake Response: {}",handshaker);
handshaker.doHandshakeResponse(request,response);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
index 1a716d5c601..28825b0a2aa 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
@@ -47,7 +47,7 @@ public class ChromeTest
{
server.stop();
}
-
+
@Test
public void testUpgradeWithWebkitDeflateExtension() throws Exception
{
@@ -59,7 +59,7 @@ public class ChromeTest
client.connect();
client.sendStandardRequest();
String response = client.expectUpgradeResponse();
- Assert.assertThat("Response", response, not(containsString("x-webkit-deflate-frame")));
+ Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame"));
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
index 15bd699efbe..2775dbb45f8 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server;
+import static org.hamcrest.Matchers.*;
+
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.junit.AfterClass;
@@ -25,9 +27,6 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.startsWith;
-
public class WebSocketInvalidVersionTest
{
private static SimpleServletServer server;
@@ -59,7 +58,7 @@ public class WebSocketInvalidVersionTest
client.sendStandardRequest();
String respHeader = client.readResponseHeader();
Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification"));
- Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13, 0\r\n"));
+ Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n"));
}
finally
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
index c1406619a39..fcf5e39bc73 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.websocket.server.blockhead;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
@@ -39,6 +42,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.net.ssl.HttpsURLConnection;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -65,12 +69,6 @@ import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.Assert;
-import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.startsWith;
-import static org.junit.Assert.assertEquals;
-
/**
* A simple websocket client for performing unit tests with.
*
@@ -219,6 +217,9 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
// Connect extensions
if (extensions != null)
{
+ generator.configureFromExtensions(extensions);
+ parser.configureFromExtensions(extensions);
+
Iterator extIter;
// Connect outgoings
extIter = extensions.iterator();
@@ -227,20 +228,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
-
- // Handle RSV reservations
- if (ext.useRsv1())
- {
- generator.setRsv1InUse(true);
- }
- if (ext.useRsv2())
- {
- generator.setRsv2InUse(true);
- }
- if (ext.useRsv3())
- {
- generator.setRsv3InUse(true);
- }
}
// Connect incomings
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
new file mode 100644
index 00000000000..fea86a59365
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
@@ -0,0 +1,125 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.server.browser;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.core.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.server.WebSocketCreator;
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
+
+/**
+ * Tool to help debug websocket circumstances reported around browsers.
+ *
+ * Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
+ * websocket implementation from the context of a browser client.
+ */
+public class BrowserDebugTool implements WebSocketCreator
+{
+ private static final Logger LOG = Log.getLogger(BrowserDebugTool.class);
+
+ public static void main(String[] args)
+ {
+ int port = 8080;
+
+ for (int i = 0; i < args.length; i++)
+ {
+ String a = args[i];
+ if ("-p".equals(a) || "--port".equals(a))
+ {
+ port = Integer.parseInt(args[++i]);
+ }
+ }
+
+ try
+ {
+ BrowserDebugTool tool = new BrowserDebugTool();
+ tool.setupServer(port);
+ tool.runForever();
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ }
+ }
+
+ private Server server;
+
+ @Override
+ public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ {
+ LOG.debug("Creating BrowserSocket");
+
+ if (req.getSubProtocols() != null)
+ {
+ if (!req.getSubProtocols().isEmpty())
+ {
+ String subProtocol = req.getSubProtocols().get(0);
+ resp.setAcceptedSubProtocol(subProtocol);
+ }
+ }
+
+ String ua = req.getHeader("User-Agent");
+ String rexts = req.getHeader("Sec-WebSocket-Extensions");
+ BrowserSocket socket = new BrowserSocket(ua,rexts);
+ return socket;
+ }
+
+ private void runForever() throws Exception
+ {
+ server.start();
+ LOG.info("Server available.");
+ server.join();
+ }
+
+ private void setupServer(int port)
+ {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(port);
+
+ server.addConnector(connector);
+
+ WebSocketHandler wsHandler = new WebSocketHandler()
+ {
+ @Override
+ public void configure(WebSocketServerFactory factory)
+ {
+ LOG.debug("Configuring WebSocketServerFactory ...");
+ factory.setCreator(BrowserDebugTool.this);
+ }
+ };
+
+ server.setHandler(wsHandler);
+
+ String resourceBase = "src/test/resources/browser-debug-tool";
+
+ ResourceHandler rHandler = new ResourceHandler();
+ rHandler.setDirectoriesListed(true);
+ rHandler.setResourceBase(resourceBase);
+ wsHandler.setHandler(rHandler);
+
+ LOG.info("{} setup on port {}",this.getClass().getName(),port);
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
new file mode 100644
index 00000000000..c03b63c60b0
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
@@ -0,0 +1,144 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.server.browser;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.core.annotations.WebSocket;
+import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
+
+@WebSocket
+public class BrowserSocket
+{
+ private static final Logger LOG = Log.getLogger(BrowserSocket.class);
+ private WebSocketConnection connection;
+ private final String userAgent;
+ private final String requestedExtensions;
+
+ public BrowserSocket(String ua, String reqExts)
+ {
+ this.userAgent = ua;
+ this.requestedExtensions = reqExts;
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(WebSocketConnection conn)
+ {
+ this.connection = conn;
+ }
+
+ @OnWebSocketClose
+ public void onDisconnect(int statusCode, String reason)
+ {
+ this.connection = null;
+ LOG.info("Closed [{}, {}]",statusCode,reason);
+ }
+
+ @OnWebSocketMessage
+ public void onTextMessage(String message)
+ {
+ LOG.info("onTextMessage({})",message);
+
+ int idx = message.indexOf(':');
+ if (idx > 0)
+ {
+ String key = message.substring(0,idx).toLowerCase();
+ String val = message.substring(idx + 1);
+ switch (key)
+ {
+ case "info":
+ {
+ if (StringUtil.isBlank(userAgent))
+ {
+ writeMessage("Client has no User-Agent");
+ }
+ else
+ {
+ writeMessage("Client User-Agent: " + this.userAgent);
+ }
+
+ if (StringUtil.isBlank(requestedExtensions))
+ {
+ writeMessage("Client requested no Sec-WebSocket-Extensions");
+ }
+ else
+ {
+ writeMessage("Client Sec-WebSocket-Extensions: " + this.requestedExtensions);
+ }
+ break;
+ }
+ case "time":
+ {
+ Calendar now = Calendar.getInstance();
+ DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
+ writeMessage("Server time: %s",sdf.format(now.getTime()));
+ break;
+ }
+ default:
+ {
+ writeMessage("key[%s] val[%s]",key,val);
+ }
+ }
+ }
+ else
+ {
+ // echo it
+ writeMessage(message);
+ }
+ }
+
+ private void writeMessage(String message)
+ {
+ if (this.connection == null)
+ {
+ LOG.debug("Not connected");
+ return;
+ }
+
+ if (connection.isOpen() == false)
+ {
+ LOG.debug("Not open");
+ return;
+ }
+
+ try
+ {
+ connection.write(null,new FutureCallback(),message);
+ }
+ catch (IOException e)
+ {
+ LOG.info(e);
+ }
+ }
+
+ private void writeMessage(String format, Object... args)
+ {
+ writeMessage(String.format(format,args));
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
new file mode 100644
index 00000000000..768afd4db50
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
@@ -0,0 +1,25 @@
+
+
+ Jetty WebSocket Browser -> Server Debug Tool
+
+
+
+
+ jetty websocket/browser/javascript -> server debug tool #console
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css
new file mode 100644
index 00000000000..9eebead468d
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css
@@ -0,0 +1,29 @@
+body {
+ font-family: sans-serif;
+}
+
+div {
+ border: 0px solid black;
+}
+
+div#console {
+ clear: both;
+ width: 40em;
+ height: 20em;
+ overflow: auto;
+ background-color: #f0f0f0;
+ padding: 4px;
+ border: 1px solid black;
+}
+
+div#console .info {
+ color: black;
+}
+
+div#console .client {
+ color: blue;
+}
+
+div#console .server {
+ color: magenta;
+}
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
new file mode 100644
index 00000000000..f92e22a3779
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
@@ -0,0 +1,121 @@
+if (!window.WebSocket && window.MozWebSocket) {
+ window.WebSocket = window.MozWebSocket;
+}
+
+if (!window.WebSocket) {
+ alert("WebSocket not supported by this browser");
+}
+
+function $() {
+ return document.getElementById(arguments[0]);
+}
+function $F() {
+ return document.getElementById(arguments[0]).value;
+}
+
+function getKeyCode(ev) {
+ if (window.event)
+ return window.event.keyCode;
+ return ev.keyCode;
+}
+
+var wstool = {
+ connect : function() {
+ var location = document.location.toString().replace('http://', 'ws://')
+ .replace('https://', 'wss://');
+
+ wstool.info("Document URI: " + document.location);
+ wstool.info("WS URI: " + location);
+
+ try {
+ this._ws = new WebSocket(location, "tool");
+ this._ws.onopen = this._onopen;
+ this._ws.onmessage = this._onmessage;
+ this._ws.onclose = this._onclose;
+ } catch (exception) {
+ wstool.info("Connect Error: " + exception);
+ }
+ },
+
+ close : function() {
+ this._ws.close();
+ },
+
+ _out : function(css, message) {
+ var console = $('console');
+ var spanText = document.createElement('span');
+ spanText.className = 'text ' + css;
+ spanText.innerHTML = message;
+ var lineBreak = document.createElement('br');
+ console.appendChild(spanText);
+ console.appendChild(lineBreak);
+ console.scrollTop = console.scrollHeight - console.clientHeight;
+ },
+
+ info : function(message) {
+ wstool._out("info", message);
+ },
+
+ infoc : function(message) {
+ wstool._out("client", "[c] " + message);
+ },
+
+ infos : function(message) {
+ wstool._out("server", "[s] " + message);
+ },
+
+ setState : function(enabled) {
+ $('connect').disabled = enabled;
+ $('close').disabled = !enabled;
+ $('info').disabled = !enabled;
+ $('time').disabled = !enabled;
+ $('hello').disabled = !enabled;
+ },
+
+ _onopen : function() {
+ wstool.setState(true);
+ wstool.info("Websocket Connected");
+ },
+
+ _send : function(message) {
+ if (this._ws) {
+ this._ws.send(message);
+ wstool.infoc(message);
+ }
+ },
+
+ write : function(text) {
+ wstool._send(text);
+ },
+
+ _onmessage : function(m) {
+ if (m.data) {
+ wstool.infos(m.data);
+ }
+ },
+
+ _onclose : function(closeEvent) {
+ this._ws = null;
+ wstool.setState(false);
+ wstool.info("Websocket Closed");
+ wstool.info(" .wasClean = " + closeEvent.wasClean);
+
+ var codeMap = {};
+ codeMap[1000] = "(NORMAL)";
+ codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
+ codeMap[1002] = "(PROTOCOL_ERROR)";
+ codeMap[1003] = "(UNSUPPORTED_DATA)";
+ codeMap[1004] = "(UNUSED/RESERVED)";
+ codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
+ codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
+ codeMap[1007] = "(BAD_DATA)";
+ codeMap[1008] = "(POLICY_VIOLATION)";
+ codeMap[1009] = "(MESSAGE_TOO_BIG)";
+ codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
+ codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
+ codeMap[1015] = "(INTERNAL/TLS_ERROR)";
+ var codeStr = codeMap[closeEvent.code];
+ wstool.info(" .code = " + closeEvent.code + " " + codeStr);
+ wstool.info(" .reason = " + closeEvent.reason);
+ }
+};
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
index e5c37fe48eb..54e14c7a74b 100644
--- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -1,5 +1,6 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
+# org.eclipse.jetty.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.io.LEVEL=DEBUG
@@ -17,6 +18,9 @@ org.eclipse.jetty.LEVEL=WARN
### See the read/write traffic
# org.eclipse.jetty.websocket.io.Frames.LEVEL=DEBUG
+### Show state changes on BrowserDebugTool
+org.eclipse.jetty.websocket.server.browser.LEVEL=DEBUG
+
### Disabling intentional error out of RFCSocket
org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF
### Disable stacks on FrameBytes.failed()
From 41ea028706e354db1ca28588cf3fc643c2cdcb62 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 4 Oct 2012 14:02:36 -0700
Subject: [PATCH 08/34] Adding websocket client example
---
.../client/examples/SimpleEchoClient.java | 127 ++++++++++++++++++
.../test/resources/jetty-logging.properties | 2 +-
2 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/SimpleEchoClient.java
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/SimpleEchoClient.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/SimpleEchoClient.java
new file mode 100644
index 00000000000..7d614bbf0ee
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/SimpleEchoClient.java
@@ -0,0 +1,127 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.examples;
+
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.client.WebSocketClientFactory;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.core.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.core.annotations.WebSocket;
+import org.eclipse.jetty.websocket.core.api.StatusCode;
+import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
+
+/**
+ * Example of a simple Echo Client.
+ */
+public class SimpleEchoClient
+{
+ @WebSocket
+ public static class SimpleEchoSocket
+ {
+ private final CountDownLatch closeLatch;
+ @SuppressWarnings("unused")
+ private WebSocketConnection conn;
+
+ public SimpleEchoSocket()
+ {
+ this.closeLatch = new CountDownLatch(1);
+ }
+
+ public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException
+ {
+ return this.closeLatch.await(duration,unit);
+ }
+
+ @OnWebSocketClose
+ public void onClose(int statusCode, String reason)
+ {
+ System.out.printf("Connection closed: %d - %s%n",statusCode,reason);
+ this.conn = null;
+ this.closeLatch.countDown(); // trigger latch
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(WebSocketConnection conn)
+ {
+ System.out.printf("Got connect: %s%n",conn);
+ this.conn = conn;
+ try
+ {
+ FutureCallback callback = new FutureCallback<>();
+ conn.write(null,callback,"Echo Me!");
+ callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
+
+ callback = new FutureCallback<>();
+ conn.write(null,callback,"Echo Another One, please.");
+ callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
+
+ conn.close(StatusCode.NORMAL,"I'm done");
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ }
+
+ @OnWebSocketMessage
+ public void onMessage(String msg)
+ {
+ System.out.printf("Got msg: %s%n",msg);
+ }
+ }
+
+ public static void main(String[] args)
+ {
+
+ WebSocketClientFactory factory = new WebSocketClientFactory();
+ SimpleEchoSocket socket = new SimpleEchoSocket();
+ try
+ {
+ factory.start();
+ WebSocketClient client = factory.newWebSocketClient(socket);
+ URI echoUri = new URI("ws://echo.websocket.org");
+ System.out.printf("Connecting to : %s%n",echoUri);
+ client.connect(echoUri);
+
+ // wait for closed socket connection.
+ socket.awaitClose(5,TimeUnit.SECONDS);
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ }
+ finally
+ {
+ try
+ {
+ factory.stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
index 14019306aed..860ec2d2761 100644
--- a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
@@ -1,7 +1,7 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=INFO
-org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
# Hide the stacktraces
From 3efefe37bc9e60ec2b9df1a8639620a56f5c7f57 Mon Sep 17 00:00:00 2001
From: Hugues Malphettes
Date: Sat, 6 Oct 2012 17:26:14 +0800
Subject: [PATCH 09/34] Clean-up OSGi test; add spdy OSGi test; fix felix
Upgrade to the latest pax-exam
Support for felix-3.x and 4.x as tested
Fix the spdy MANIFEST.MF generations
Add an integration test for SPDY
Refactor the test code
Squashed commit of the following:
commit bd020ee1214992d8d21a11dc800e04dc5e9b2001
Author: Hugues Malphettes
Date: Sat Oct 6 16:58:43 2012 +0800
Add spdy integration test for OSGi and clean-up
Refacor the pax-exam OSGi integration tests
Add an integration test for spdy.
Execute the test under 2 versions of felix and 2 versions of equinox.
commit f3151a272ab92560432f3b76f564bf06b19bc22b
Author: Hugues Malphettes
Date: Sat Oct 6 16:28:51 2012 +0800
Fix the generated MANIFEST.MF
OSGi integration test in the next commit.
commit 3152aa2b5e39cf2d3b81f8400488c0672e922b8d
Author: Hugues Malphettes
Date: Fri Oct 5 16:58:29 2012 +0800
Fix the startup of the servlet.
setInitOrder(0) was working in jetty-7 and jetty-8 but not in jetty-9
anymore. setInitOrder(1) is fine.
commit 8038d314f4f423e8608fd09dd42b840e101a0c13
Author: Hugues Malphettes
Date: Thu Oct 4 17:53:28 2012 +0800
Upgrade to pax-exam-2.6
commit 7136fa88e2410ac345b6ae0657d882c7e9714c0b
Author: Hugues Malphettes
Date: Thu Oct 4 17:53:07 2012 +0800
Support for felix-3.x and felix-4.x
commit 0bcc6b0d8ed5144150f90f578a90c558419349d1
Author: Hugues Malphettes
Date: Thu Oct 4 17:53:28 2012 +0800
Upgrade to pax-exam-2.6
commit 2e17466624650df433b6c5f11abafb56539ee740
Author: Hugues Malphettes
Date: Thu Oct 4 17:53:07 2012 +0800
Support for felix-3.x and felix-4.x
---
.../DefaultBundleClassLoaderHelper.java | 51 ++-
.../internal/DefaultFileLocatorHelper.java | 94 ++++-
.../contexts/httpservice.xml | 2 +-
jetty-osgi/pom.xml | 4 +-
jetty-osgi/test-jetty-osgi/pom.xml | 331 +++++++++++-------
.../main/resources/jetty-logging.properties | 2 +
.../src/main/resources/keystore.jks | Bin 0 -> 2206 bytes
.../src/main/resources/truststore.jks | Bin 0 -> 916 bytes
.../src/test/config/etc/jetty-spdy.xml | 133 +++++++
.../src/test/config/etc/keystore | Bin 0 -> 1416 bytes
.../osgi/boot/TestJettyOSGiBootCore.java | 197 -----------
.../osgi/boot/TestJettyOSGiBootWithJsp.java | 172 ---------
.../jetty/osgi/test/AbstractTestOSGi.java | 188 ++++++++++
.../osgi/test/TestJettyOSGiBootCore.java | 118 +++++++
.../osgi/test/TestJettyOSGiBootSpdy.java | 125 +++++++
.../osgi/test/TestJettyOSGiBootWithJsp.java | 180 ++++++++++
jetty-plus/pom.xml | 4 +-
jetty-spdy/pom.xml | 1 +
jetty-spdy/spdy-client/pom.xml | 14 +-
jetty-spdy/spdy-core/pom.xml | 32 --
jetty-spdy/spdy-http-server/pom.xml | 12 +-
jetty-spdy/spdy-server/pom.xml | 16 +-
pom.xml | 1 +
23 files changed, 1103 insertions(+), 574 deletions(-)
create mode 100644 jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties
create mode 100644 jetty-osgi/test-jetty-osgi/src/main/resources/keystore.jks
create mode 100644 jetty-osgi/test-jetty-osgi/src/main/resources/truststore.jks
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore
delete mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootCore.java
delete mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWithJsp.java
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/AbstractTestOSGi.java
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
create mode 100644 jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
index 8633033a00f..69d2013cf75 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
@@ -33,6 +33,10 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
private static boolean identifiedOsgiImpl = false;
+
+ private static Class BundleWiringClass = null;
+ private static Method BundleWiringClass_getClassLoader_method = null;
+ private static Method BundleClass_adapt_method = null;
private static boolean isEquinox = false;
@@ -41,13 +45,34 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
private static void init(Bundle bundle)
{
identifiedOsgiImpl = true;
+
try
{
- isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null;
+ BundleWiringClass = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring");
+ if (BundleWiringClass != null)
+ {
+ BundleWiringClass_getClassLoader_method = BundleWiringClass.getDeclaredMethod("getClassLoader", new Class[] {});
+ BundleClass_adapt_method = bundle.getClass().getDeclaredMethod("adapt", new Class[] { Class.class });
+ BundleClass_adapt_method.setAccessible(true);
+ return;
+ }
}
catch (Throwable t)
{
- isEquinox = false;
+ //nevermind: an older version of OSGi where BundleWiring is not availble
+ //t.printStackTrace();
+ }
+
+ if (!bundle.getClass().getName().startsWith("org.apache.felix"))
+ {
+ try
+ {
+ isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null;
+ }
+ catch (Throwable t)
+ {
+ isEquinox = false;
+ }
}
if (!isEquinox)
{
@@ -70,6 +95,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
*/
public ClassLoader getBundleClassLoader(Bundle bundle)
{
+ //Older OSGi implementations:
String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator");
if (bundleActivator == null)
{
@@ -93,6 +119,22 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
init(bundle);
}
+ //This works for OSGi 4.2 and more recent. Aka version 1.6
+ //It is using ava reflection to execute:
+ //(BundleClassLoader) bundle.adapt(BundleWiring.class).getClassLoader()
+ if (BundleClass_adapt_method != null && BundleWiringClass_getClassLoader_method != null)
+ {
+ try
+ {
+ Object bundleWiring = BundleClass_adapt_method.invoke(bundle, BundleWiringClass);
+ return (ClassLoader)BundleWiringClass_getClassLoader_method.invoke(bundleWiring, new Object[] {});
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ return null;
+ }
+ }
if (isEquinox)
{
return internalGetEquinoxBundleClassLoader(bundle);
@@ -135,13 +177,16 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
private static Field Felix_BundleImpl_m_modules_field;
private static Field Felix_ModuleImpl_m_classLoader_field;
+
+ private static Field Felix_BundleImpl_m_revisions_field;
+
private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle)
{
// assume felix:
try
{
- // now get the current module from the bundle.
+ // now get the current module from the bundle.
// and return the private field m_classLoader of ModuleImpl
if (Felix_BundleImpl_m_modules_field == null)
{
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
index 24e568c532f..af2d3c3e9a4 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
@@ -36,8 +36,8 @@ import org.eclipse.jetty.util.resource.FileResource;
import org.osgi.framework.Bundle;
/**
- * From a bundle to its location on the filesystem. Assumes the bundle is not a
- * jar.
+ * From a bundle to its location on the filesystem.
+ * Often assumes the bundle is not a jar.
*
* @author hmalphettes
*/
@@ -150,7 +150,6 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
{
// observed this on felix-2.0.0
String location = bundle.getLocation();
- // System.err.println("location " + location);
if (location.startsWith("file:/"))
{
URI uri = new URI(URIUtil.encodePath(location));
@@ -183,10 +182,16 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
File file = new File(location.substring("file:".length()));
return file;
}
+ else
+ {
+ //Resort to introspection on felix:
+ return getBundleInstallLocationInFelix(bundle);
+ }
}
return null;
}
-
+
+
/**
* Locate a file inside a bundle.
*
@@ -357,4 +362,85 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
return url;
}
+ // Felix introspection
+ private static Method Felix_BundleImpl_getArchive_method;
+ private static Method Felix_BundleArchive_getCurrentRevision_method;
+ private static Method Felix_BundleRevision_getRevisionRootDir_method;
+
+ private static boolean felixIntroSpectionDone = false;
+
+ /**
+ * Introspection of the implementation classes of Felix-3.x and Felix-4.x.
+ *
+ * See org.apache.felix.framework.cache
+ * In pseudo code:
+ *
+ * File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir();
+ * return new File(revRootDir, bundle.jar) if it exists?
+ * else return revRootDir
+ *