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 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 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 + *

+ * @param bundle + * @return The File or null if we failed to find it. + */ + private static File getBundleInstallLocationInFelix(Bundle bundle) + { + if (Felix_BundleImpl_getArchive_method == null) { + if (felixIntroSpectionDone) + { + return null; + } + felixIntroSpectionDone = true; + try + { + Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {}); + Felix_BundleImpl_getArchive_method.setAccessible(true); + Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); + Class bundleArchiveClass = archive.getClass(); + Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {}); + Felix_BundleArchive_getCurrentRevision_method.setAccessible(true); + Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); + Class bundleRevisionClass = revision.getClass(); + Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {}); + Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true); + } + catch (Throwable t) + { + //nevermind? + //t.printStackTrace(); + Felix_BundleImpl_getArchive_method = null; + return null; + } + } + if (Felix_BundleImpl_getArchive_method != null) + { + try + { + Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); + Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); + File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision); + //System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath()); + File bundleJar = new File(revRootDir, "bundle.jar"); + if (bundleJar.exists()) + { + //bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision + //when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already + //taken care if that scheme earlier. + return bundleJar; + } + else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists()) + { + //this is a DirectoryRevision + return revRootDir; + } + } + catch (Throwable t) + { + //best effort: nevermind + //t.printStackTrace(); + } + } + return null; + } +// -- end Felix introspection + } diff --git a/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml b/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml index 211a8e8ce28..e39508c26a2 100644 --- a/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml +++ b/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml @@ -23,7 +23,7 @@ org.eclipse.equinox.http.servlet.HttpServiceServlet /* - 0 + 1 diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index 08868f4155f..24d1b7c9dd5 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -15,8 +15,8 @@ 3.2.100.v20100503 1.0.0-v20070606 - 0.9.18 - 1.5.11 + 0.9.29 + 1.6.1 jetty-osgi-boot diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 905b7e8a5bc..0dacc7b1955 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -8,28 +8,157 @@ 4.0.0 test-jetty-osgi Jetty :: OSGi :: Test - Jetty OSGi Integration test + Jetty OSGi Integration tests - ${project.groupId}.boot.test + ${project.groupId}.boot.test.spdy http://download.eclipse.org/jetty/orbit/ target/distribution - 1.2.0 - 1.2.4 + 2.6.0 + 1.4.0 + 1.5.1 + 4.0.3 + 1.0 + 1.7.6 + 1.1.0.v20120525 + + junit + junit-dep + 4.10 + + + + + org.ops4j.pax.swissbox + pax-swissbox-core + ${paxswissbox.version} + test + + + org.ops4j.pax.swissbox + pax-swissbox-extender + ${paxswissbox.version} + test + + + org.ops4j.pax.swissbox + pax-swissbox-lifecycle + ${paxswissbox.version} + test + + + org.ops4j.pax.swissbox + pax-swissbox-framework + ${paxswissbox.version} + test + + + org.ops4j.pax.exam + pax-exam + ${exam.version} + test + + + org.apache.geronimo.specs + geronimo-atinject_1.0_spec + ${injection.bundle.version} + test + + + org.ops4j.pax.exam + pax-exam-inject + ${exam.version} + test + + + + + + + + + org.ops4j.pax.exam + pax-exam-container-paxrunner + ${exam.version} + test + + + + org.ops4j.pax.runner + pax-runner-no-jcl + ${runner.version} + test + + + + org.ops4j.pax.exam + pax-exam-junit4 + ${exam.version} + test + + + junit + junit + + + + + org.ops4j.pax.exam + pax-exam-link-mvn + ${exam.version} + test + + + org.ops4j.pax.url + pax-url-aether + ${url.version} + test + + + + org.apache.felix + org.apache.felix.framework + ${felixversion} + test + + + org.ops4j.pax.exam + pax-exam-testforge + ${exam.version} + test + + + + org.slf4j + slf4j-simple + 1.6.1 + test + org.eclipse.jetty.orbit javax.servlet + test - - - org.eclipse.jetty - jetty-jsp - ${project.version} - - org.eclipse.jetty.osgi @@ -49,7 +178,6 @@ ${project.version} provided - org.eclipse.jetty @@ -123,13 +251,44 @@ ${project.version} runtime + + + org.eclipse.jetty.spdy + spdy-core + ${project.version} + test + + + org.eclipse.jetty.spdy + spdy-server + ${project.version} + test + + + org.eclipse.jetty.spdy + spdy-http-server + ${project.version} + test + + + org.eclipse.jetty.spdy + spdy-client + ${project.version} + test + + + org.mortbay.jetty.npn + npn-boot + ${npn-version} + test + + org.eclipse.jetty jetty-plus ${project.version} runtime - org.eclipse.osgi @@ -153,7 +312,6 @@ servlet runtime - org.eclipse.jetty test-jetty-webapp @@ -161,139 +319,40 @@ webbundle test - - - org.ops4j.pax.exam - pax-exam - ${paxexam-version} - test - - - org.ops4j.pax.exam - pax-exam-junit - ${paxexam-version} - test - - - - junit - junit - - - - - org.ops4j.pax.exam - pax-exam-container-default - ${paxexam-version} - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - org.slf4j - slf4j-api - provided - - - org.slf4j - slf4j-simple - ${slf4j-version} - provided - - - - org.ops4j.pax.exam - maven-paxexam-plugin - ${paxexam-version} + maven-surefire-plugin + + + + + -Dmortbay-npn-boot=${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 1.7 + 1.7 + + + + org.apache.servicemix.tooling + depends-maven-plugin + 1.2 - generate-config + generate-depends-file generate-depends-file - generate-config - - - ${project.build.directory}/paxexam - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-orbit-servlet-api-deps - generate-resources - - copy - - - - - org.eclipse.jetty.orbit - javax.servlet - ${orbit-servlet-api-version} - true - ${assembly-directory}/lib - servlet-api-3.0.jar - - - - - - copy-orbit-lib-jndi-deps - generate-resources - - copy-dependencies - - - org.eclipse.jetty.orbit - javax.mail.glassfish,javax.activation - jar - ${assembly-directory}/lib/jndi - - - - copy-orbit-lib-jsp-deps - generate-resources - - copy-dependencies - - - org.eclipse.jetty.orbit - com.sun.el,javax.el,javax.servlet.jsp,javax.servlet.jsp.jstl,org.apache.jasper.glassfish,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core - jar - ${assembly-directory}/lib/jsp - - - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.8 - - prevent/overwriting/by/pointing/to/nonexisting/MANIFEST.MF - false - true - - **/.svn/** - - - - + \ No newline at end of file diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties new file mode 100644 index 00000000000..5250a08562a --- /dev/null +++ b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties @@ -0,0 +1,2 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +org.eclipse.jetty.spdy.LEVEL=WARN diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/keystore.jks b/jetty-osgi/test-jetty-osgi/src/main/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..428ba54776ede2fdcdeedd879edb927c2abd9953 GIT binary patch literal 2206 zcmcgt`9Bkm8{cNkoMUp6gmShKn!AQX*(l6Nj(i=TnQPOKYtv{*Wg>ItE=Q!pRYH8a z$Sp#S#2lYw#aw;$y9u4T}83H*%lp zAKZay0sy=q1Qoo85aAQh;$ zD(c2EIN#D7WwYDLKUg!CotQPD@dp;5FR#bgaace(^x$6g5frD~(_b(MI^J&*A2DRp zf5Q2onfE(zvUb9|9C`66)YFRNM6~xrz4;iVbU=P|*YT2eWHFJJtr+M@zt2qPm)K~rRcqcs=LM12)PX0TT%QO zlf*xkqD3}7l)1J`5W(>=9nR0e6j-<79<11v3ZuXXcQpoCsqY~n`$FN+S}hcVm5Y>G zXnD{@DYs1@{S0z(lW+?86LWKtku$$-(khsh>0qRUXn=84`GRn?77M^_JY`durnN;KE zW#OJ`h<6xcB{I))ekGpc*Ylt}0cx4|OMBDPQvx4`r`}4Ze5_ipdObGMTi3bZHd5PC zcY0;?uBWu$PSvjJeb87nY7ghNv?%M@SoDl6IWt`bQCosfSh$#D6$ea~QhKM^ud2Ut z+9PYJuVpoELmN-A`F$BicO{BSYg@#tS%avVfb}DxL)|NanJ)#zB!2~?#Ot%H7--9N zU$bs0fS5G!m5M4&WK3#a|H|Tgw*?X-;H+Lu@kwA>qSR~7UC7b)7MJXTn6PG>n@8jP zW+}F^X$$c;U~4ryqRF; z>`j!tbLMK4ZGyY643|~?%Mu#fm!l%wAKjBDmd+VYmp3S#$scD$~bxbf|z#)hShN0*AhRaPDcmqrftGlHq4^54MM$Xfy(2> zH8QYVMzmn_oHbvJCB`IN~E&{1*h&0gEM{e zKvWvzp(!BqMX8`t#)~0nq}Wa zr6>FRPyp;AAB&)1$5@;r$23J{K&~>TWjZf7V$wFzmGM95CXhFG1cJNVAXks}C+&2- zbf9Qn*D8N}Afd2kpwDxns3%1uaFhAqDV8ksWiWY|quuLGZ0)SqrJ!Y8yX}@}IyC$C zQ3rCUsn}#>F#D8%D?q~ySy4j&he%Bs{{7V%rl!ui`@KQP?NTi+_iN{cwom&9RaMRR zB~z!hz|0HAgB9_Ijvpe-zr#jLbckJsc>vmo{+im?t8lA;N#fD4?{lb&J0V8Gocq%; f1ihv=QIDh{M_<9V+45Z2{KE4_qW}V3B0uV%GgrOJ literal 0 HcmV?d00001 diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/truststore.jks b/jetty-osgi/test-jetty-osgi/src/main/resources/truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..839cb8c35151c2b7c64afca24b6b72caad070a05 GIT binary patch literal 916 zcmezO_TO6u1_mY|W(3o$xs} zE~X|%Muz1J{3AIFGbaABoD&*5saD@gH|APIn|qhRGl}gsUzm=o9G*UXZaLfkb^*)o zjA*-gTf)`m_MQJYE&gJ}p^PHkrj!4^W|XX5a=N7A{;n#yaON&k_bHloe-^*hm?Z91 zlB>xeD=<(C>yn{9D54u}krkl}HQ(Uscha(++qf!T9y+xaEfnXd1O zi0)T?voO%;QH9LK;*_O3mBblqm)!31vU@hm;^%>mh5U@y3R%l0gzi`2yxH!+?kPOi zt!Tnsz1x9B3U2~8STZp)GB6^C5HPs_Lx_=~O<3xi>MmQ;D_g$D<_pdct`+TyzWTQ= zW5Finm(sGEe;ty^>vg$!cV)t>;H#Mev23$*WWBpyJ}Ir;RW+Htrt6{Pk&qz&-XG2@ z8@{&Lu%DX7m47Uny+-3w`=4V611q#Ub(U`xZCtSK^2LO^3(s|HW&N14dV4@A&(kX% z*S_eUPs-bSWRp>avt;CP@7K+G&3=b&1eO-s3f`;Cf91p#$)FW&xME3L8sEBQQDVCvfG>mdwqnk+GXd2ihXqpv z;usF(WoYYmu8DZZa4%1z=+hI+*gpkUykAy5tj#grb*gH!M6TqIcifYBGVe^&T#-2O K*=+x>r_BKeJV|!| literal 0 HcmV?d00001 diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml new file mode 100644 index 00000000000..2e24a468c07 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml @@ -0,0 +1,133 @@ + + + + + + + + + + https + + 32768 + 8192 + 8192 + + + + + + + + + + + + /etc/keystore + + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + OBF:1u2u1wml1z7s1z7a1wnl1u2g + /etc/keystore + + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + + + + + + + + + + + + + + + + + + + + .*\.css + .*\.js + .*\.png + .*\.jpg + .*\.gif + + + + + + + + + + + + + + + + + + npn + + + + + + + + + + + spdy/3 + spdy/2 + http/1.1 + + + http/1.1 + + + + + + 3 + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + 30000 + + + + + diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore b/jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore new file mode 100644 index 0000000000000000000000000000000000000000..08f6cda8a7b0104236c37f3c06b4cda1d8fc58e6 GIT binary patch literal 1416 zcmezO_TO6u1_mY|W&~r_tkjZ{N+3_R)UekNZbhDKH`EXUR-=hO7hY&y{H-e(+TowuJ*{`? zwVfT`ns@KmCLd9_H?QZ%#(VNFq-*~l&ib%)(Y!;gI{W`RJIFow^~EB{E~hHzXm8Qd z$+I3OFWL4l%D`0pEH}^DdFqAf_3vNy-07jRmW@8^t|?HpH{w+9zJZ0HTQ{GwgE`KsL0)*BinM>ExK{8V|s z_c83?=Ue&W%>UG?wT~>g^C-D5b6Kid$utMyOUY~8&RQ2O5Kv$6WA72RZCZOI`%XW7 zWGv*iET~D-A?Uf$)PkAXC#^&coOKW1)_jbSp1ybc*-Pg z*1AKX?hJ-g?<}Z|$~fj~%&Q)8GVXHmuY_w7iL*9Li<#ba=>(4shvkf&zalFllPfRmB({Gr>G+(AG{|j5^yi-$J^G%gy&!3%F`Y=?Z zqHag3(zm<4?6(-6S8caBa7ykAzsvLfb>|ds{`&FW?ULoW&INs`nHskiv_4^yUS(;u zwJG}=M=euvt`d*!{pAW3tsAR0lWEH+#d4(r%9_{jEh_BIde_H z2F(udYbrB!9Sz@xPFu0!#>2SI`Ssat!p3^(;?40Uy|?vOtS!3O8Zd1|F}L60tKCOm znylYEA@-w}ri%BqU%N_+XGl&zI@4*vv5nXG(`N27=6zy$WG5s+N9dUvSOODr2QVSG z7&I}yWn%FZKK#XimyJ`a&7GmI50%m z5h5xEN+4Za!qUF^MI{POiIob@`FX{qIVG8S=?VcQl?py3DTaIoJRnuv!mM7P3}z^0 zAOYet3k!lXoL(+aZ&G5VUVc%!ft)z6frX)=fw_T+k%h5YlsK=knSrs9DU>^yoZ2`a zIUIo{19M|9Ff2Qn8XFm!A3o&OeJrUOYjfQ^)giy{^|QK1CEP2QH%vD1Nc3MB^xmW4 z2fxzex2hhF*O)r=%WpirQ+H{vy7qk+o=56U7bdiMykRRo{_oVH)zDhW2yL+!vyn`wiA33|lR42Pgou}g~dvauF+U45K+=Wccj0}v( z&I3j>GtgZkrPhJg=H9<%l_$GC*-}{lrfI>Hv$DOvyPj6+9TS$eJZ37ao71_#A!EVD zCY$?z=d9ULtT&}=o8QIxpMTDAp0ptI@ccyM2W9WgVozM<*}KLmGg5O$WNJ zyVS~6#D7};!ib^4U#WeU%`4Hj5}6$f=N+nWnZl~&!Rl~+N3e provisionCoreJetty() - { - List