diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
index 44dfbcc986d..6d04aaccd24 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
@@ -21,13 +21,6 @@ package org.eclipse.jetty.embedded;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
-/* ------------------------------------------------------------ */
-/**
- * A {@link ContextHandler} provides a common environment for multiple Handlers,
- * such as: URI context path, class loader, static resource base.
- *
- * Typically a ContextHandler is used only when multiple contexts are likely.
- */
public class OneContext
{
public static void main(String[] args) throws Exception
@@ -38,10 +31,10 @@ public class OneContext
context.setContextPath("/");
context.setResourceBase(".");
context.setClassLoader(Thread.currentThread().getContextClassLoader());
- server.setHandler(context);
-
context.setHandler(new HelloHandler());
+ server.setHandler(context);
+
server.start();
server.join();
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index 59082e104ee..dabee2ce127 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
@@ -26,6 +26,7 @@ import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.jar.JarEntry;
import org.eclipse.jetty.util.Loader;
@@ -807,7 +808,7 @@ public class AnnotationParser
try
{
String name = entry.getName();
- if (name.toLowerCase().endsWith(".class"))
+ if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
{
String shortName = name.replace('/', '.').substring(0,name.length()-6);
if ((resolver == null)
@@ -853,7 +854,7 @@ public class AnnotationParser
try
{
String name = entry.getName();
- if (name.toLowerCase().endsWith(".class"))
+ if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
{
String shortName = name.replace('/', '.').substring(0,name.length()-6);
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
index 0f15b2a7239..7f03c79e06e 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.annotations;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.util.Locale;
import javax.annotation.Resource;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
@@ -261,7 +262,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//default name is the javabean property name
String name = method.getName().substring(3);
- name = name.substring(0,1).toLowerCase()+name.substring(1);
+ name = name.substring(0,1).toLowerCase(Locale.ENGLISH)+name.substring(1);
name = clazz.getCanonicalName()+"/"+name;
name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index eaeff4bfb45..c1e2dd722fa 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -79,14 +79,14 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public void onComplete(Result result)
{
Request request = result.getRequest();
- HttpConversation conversation = client.getConversation(request.getConversationID());
- Response.Listener listener = conversation.getExchanges().peekFirst().getResponseListener();
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ List listeners = conversation.getExchanges().peekFirst().getResponseListeners();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
if (result.isFailed())
{
Throwable failure = result.getFailure();
LOG.debug("Authentication challenge failed {}", failure);
- notifier.forwardFailureComplete(listener, request, result.getRequestFailure(), response, result.getResponseFailure());
+ notifier.forwardFailureComplete(listeners, request, result.getRequestFailure(), response, result.getResponseFailure());
return;
}
@@ -94,7 +94,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (wwwAuthenticates.isEmpty())
{
LOG.debug("Authentication challenge without WWW-Authenticate header");
- notifier.forwardFailureComplete(listener, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
+ notifier.forwardFailureComplete(listeners, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
return;
}
@@ -113,7 +113,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (authentication == null)
{
LOG.debug("No authentication available for {}", request);
- notifier.forwardSuccessComplete(listener, request, response);
+ notifier.forwardSuccessComplete(listeners, request, response);
return;
}
@@ -121,19 +121,20 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
{
- notifier.forwardSuccessComplete(listener, request, response);
+ notifier.forwardSuccessComplete(listeners, request, response);
return;
}
- authnResult.apply(request);
- request.send(new Response.Listener.Empty()
+ Request newRequest = client.copyRequest(request, request.getURI());
+ authnResult.apply(newRequest);
+ newRequest.onResponseSuccess(new Response.SuccessListener()
{
@Override
public void onSuccess(Response response)
{
client.getAuthenticationStore().addAuthenticationResult(authnResult);
}
- });
+ }).send(null);
}
private List parseWWWAuthenticate(Response response)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
index 0e530d049c0..8588914571a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
@@ -18,8 +18,11 @@
package org.eclipse.jetty.client;
+import java.util.List;
+
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
@@ -41,7 +44,8 @@ public class ContinueProtocolHandler implements ProtocolHandler
public boolean accept(Request request, Response response)
{
boolean expect100 = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
- boolean handled100 = client.getConversation(request.getConversationID()).getAttribute(ATTRIBUTE) != null;
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ boolean handled100 = conversation != null && conversation.getAttribute(ATTRIBUTE) != null;
return expect100 && !handled100;
}
@@ -60,20 +64,20 @@ public class ContinueProtocolHandler implements ProtocolHandler
// Handling of success must be done here and not from onComplete(),
// since the onComplete() is not invoked because the request is not completed yet.
- HttpConversation conversation = client.getConversation(response.getConversationID());
+ HttpConversation conversation = client.getConversation(response.getConversationID(), false);
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
HttpExchange exchange = conversation.getExchanges().peekLast();
assert exchange.getResponse() == response;
- Response.Listener listener = exchange.getResponseListener();
+ List listeners = exchange.getResponseListeners();
switch (response.getStatus())
{
case 100:
{
// All good, continue
exchange.resetResponse(true);
- conversation.setResponseListener(listener);
+ conversation.setResponseListeners(listeners);
exchange.proceed(true);
break;
}
@@ -81,8 +85,8 @@ public class ContinueProtocolHandler implements ProtocolHandler
{
// Server either does not support 100 Continue, or it does and wants to refuse the request content
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
- notifier.forwardSuccess(listener, contentResponse);
- conversation.setResponseListener(listener);
+ notifier.forwardSuccess(listeners, contentResponse);
+ conversation.setResponseListeners(listeners);
exchange.proceed(false);
break;
}
@@ -92,15 +96,20 @@ public class ContinueProtocolHandler implements ProtocolHandler
@Override
public void onFailure(Response response, Throwable failure)
{
- HttpConversation conversation = client.getConversation(response.getConversationID());
+ HttpConversation conversation = client.getConversation(response.getConversationID(), false);
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
HttpExchange exchange = conversation.getExchanges().peekLast();
assert exchange.getResponse() == response;
- Response.Listener listener = exchange.getResponseListener();
+ List listeners = exchange.getResponseListeners();
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
- notifier.forwardFailureComplete(listener, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
+ notifier.forwardFailureComplete(listeners, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
+ }
+
+ @Override
+ public void onComplete(Result result)
+ {
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/DoubleResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/DoubleResponseListener.java
deleted file mode 100644
index ba2587bc203..00000000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/DoubleResponseListener.java
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-// ========================================================================
-// 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.client;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
-
-public class DoubleResponseListener implements Response.Listener
-{
- private final ResponseNotifier responseNotifier;
- private final Response.Listener listener1;
- private final Response.Listener listener2;
-
- public DoubleResponseListener(ResponseNotifier responseNotifier, Response.Listener listener1, Response.Listener listener2)
- {
- this.responseNotifier = responseNotifier;
- this.listener1 = listener1;
- this.listener2 = listener2;
- }
-
- @Override
- public void onBegin(Response response)
- {
- responseNotifier.notifyBegin(listener1, response);
- responseNotifier.notifyBegin(listener2, response);
- }
-
- @Override
- public void onHeaders(Response response)
- {
- responseNotifier.notifyHeaders(listener1, response);
- responseNotifier.notifyHeaders(listener2, response);
- }
-
- @Override
- public void onContent(Response response, ByteBuffer content)
- {
- responseNotifier.notifyContent(listener1, response, content);
- responseNotifier.notifyContent(listener2, response, content);
- }
-
- @Override
- public void onSuccess(Response response)
- {
- responseNotifier.notifySuccess(listener1, response);
- responseNotifier.notifySuccess(listener2, response);
- }
-
- @Override
- public void onFailure(Response response, Throwable failure)
- {
- responseNotifier.notifyFailure(listener1, response, failure);
- responseNotifier.notifyFailure(listener2, response, failure);
- }
-
- @Override
- public void onComplete(Result result)
- {
- responseNotifier.notifyComplete(listener1, result);
- responseNotifier.notifyComplete(listener2, result);
- }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 1b1f7cffa57..ab074d21b01 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -45,6 +46,8 @@ import org.eclipse.jetty.client.api.CookieStore;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
@@ -86,7 +89,7 @@ import org.eclipse.jetty.util.thread.TimerScheduler;
*
* // Asynchronously
* HttpClient client = new HttpClient();
- * client.newRequest("http://localhost:8080").send(new Response.Listener.Adapter()
+ * client.newRequest("http://localhost:8080").send(new Response.Listener.Empty()
* {
* @Override
* public void onSuccess(Response response)
@@ -265,9 +268,21 @@ public class HttpClient extends ContainerLifeCycle
return new HttpRequest(this, uri);
}
- protected Request newRequest(long id, String uri)
+ protected Request copyRequest(Request oldRequest, String newURI)
{
- return new HttpRequest(this, id, URI.create(uri));
+ Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), URI.create(newURI));
+ newRequest.method(oldRequest.getMethod())
+ .version(oldRequest.getVersion())
+ .content(oldRequest.getContent());
+ for (HttpFields.Field header : oldRequest.getHeaders())
+ {
+ // We have a new URI, so skip the host header if present
+ if (HttpHeader.HOST == header.getHeader())
+ continue;
+
+ newRequest.header(header.getName(), header.getValue());
+ }
+ return newRequest;
}
private String address(String scheme, String host, int port)
@@ -307,9 +322,9 @@ public class HttpClient extends ContainerLifeCycle
return new ArrayList(destinations.values());
}
- protected void send(final Request request, Response.Listener listener)
+ protected void send(final Request request, List listeners)
{
- String scheme = request.getScheme().toLowerCase();
+ String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
if (!Arrays.asList("http", "https").contains(scheme))
throw new IllegalArgumentException("Invalid protocol " + scheme);
@@ -317,11 +332,12 @@ public class HttpClient extends ContainerLifeCycle
if (port < 0)
port = "https".equals(scheme) ? 443 : 80;
- if (listener instanceof ResponseListener.Timed)
- ((ResponseListener.Timed)listener).schedule(scheduler);
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Schedulable)
+ ((Schedulable)listener).schedule(scheduler);
HttpDestination destination = provideDestination(scheme, request.getHost(), port);
- destination.send(request, listener);
+ destination.send(request, listeners);
}
protected void newConnection(HttpDestination destination, Callback callback)
@@ -365,10 +381,10 @@ public class HttpClient extends ContainerLifeCycle
}
}
- protected HttpConversation getConversation(long id)
+ protected HttpConversation getConversation(long id, boolean create)
{
HttpConversation conversation = conversations.get(id);
- if (conversation == null)
+ if (conversation == null && create)
{
conversation = new HttpConversation(this, id);
HttpConversation existing = conversations.putIfAbsent(id, conversation);
@@ -395,7 +411,7 @@ public class HttpClient extends ContainerLifeCycle
{
for (ProtocolHandler handler : getProtocolHandlers())
{
- if (handler.accept(request, response))
+ if (handler.accept(request, response))
return handler;
}
return null;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
index c5485984887..aae92bcac98 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
@@ -102,7 +103,12 @@ public class HttpConnection extends AbstractConnection implements Connection
}
@Override
- public void send(Request request, Response.Listener listener)
+ public void send(Request request, Response.CompleteListener listener)
+ {
+ send(request, Collections.singletonList(listener));
+ }
+
+ public void send(Request request, List listeners)
{
normalizeRequest(request);
@@ -111,13 +117,14 @@ public class HttpConnection extends AbstractConnection implements Connection
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.getIdleTimeout());
- HttpConversation conversation = client.getConversation(request.getConversationID());
- HttpExchange exchange = new HttpExchange(conversation, this, request, listener);
+ HttpConversation conversation = client.getConversation(request.getConversationID(), true);
+ HttpExchange exchange = new HttpExchange(conversation, this, request, listeners);
setExchange(exchange);
conversation.getExchanges().offer(exchange);
- if (listener instanceof ResponseListener.Timed)
- ((ResponseListener.Timed)listener).schedule(client.getScheduler());
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Schedulable)
+ ((Schedulable)listener).schedule(client.getScheduler());
sender.send(exchange);
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
index 97aad12d9ad..aa9dd777eab 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.List;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
@@ -46,9 +47,9 @@ public class HttpContentResponse implements ContentResponse
}
@Override
- public Listener getListener()
+ public List getListeners(Class listenerClass)
{
- return response.getListener();
+ return response.getListeners(listenerClass);
}
@Override
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
index 5e9e6b7d69c..bacb391e20e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.util.Collections;
import java.util.Deque;
import java.util.Enumeration;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
@@ -34,8 +35,7 @@ public class HttpConversation implements Attributes
private final Deque exchanges = new ConcurrentLinkedDeque<>();
private final HttpClient client;
private final long id;
- private volatile Response.Listener listener;
- private volatile HttpExchange last;
+ private volatile List listeners;
public HttpConversation(HttpClient client, long id)
{
@@ -53,35 +53,14 @@ public class HttpConversation implements Attributes
return exchanges;
}
- public Response.Listener getResponseListener()
+ public List getResponseListeners()
{
- return listener;
+ return listeners;
}
- public void setResponseListener(Response.Listener listener)
+ public void setResponseListeners(List listeners)
{
- this.listener = listener;
- }
-
- /**
- * @return the exchange that has been identified as the last of this conversation
- * @see #last
- */
- public HttpExchange getLastExchange()
- {
- return last;
- }
-
- /**
- * Remembers the given {@code exchange} as the last of this conversation.
- *
- * @param exchange the exchange that is the last of this conversation
- * @see #last
- */
- public void setLastExchange(HttpExchange exchange)
- {
- if (last == null)
- last = exchange;
+ this.listeners = listeners;
}
public void complete()
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java
index 8a7fbca3772..0651fe2636c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieParser.java
@@ -44,7 +44,7 @@ public class HttpCookieParser
public static List parseCookies(String headerValue)
{
- if (headerValue.toLowerCase().contains("expires="))
+ if (headerValue.toLowerCase(Locale.ENGLISH).contains("expires="))
{
HttpCookie cookie = parseCookie(headerValue, 0);
if (cookie != null)
@@ -111,7 +111,7 @@ public class HttpCookieParser
try
{
String[] attributeParts = cookieParts[i].split("=", 2);
- String attributeName = attributeParts[0].trim().toLowerCase();
+ String attributeName = attributeParts[0].trim().toLowerCase(Locale.ENGLISH);
String attributeValue = attributeParts.length < 2 ? "" : attributeParts[1].trim();
switch (attributeName)
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index a9a66d2b657..489826a477b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -50,7 +50,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final String scheme;
private final String host;
private final int port;
- private final Queue requests;
+ private final Queue requests;
private final BlockingQueue idleConnections;
private final BlockingQueue activeConnections;
private final RequestNotifier requestNotifier;
@@ -97,7 +97,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return port;
}
- public void send(Request request, Response.Listener listener)
+ public void send(Request request, List listeners)
{
if (!scheme.equals(request.getScheme()))
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
@@ -107,12 +107,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
if (port >= 0 && this.port != port)
throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this);
- RequestPair requestPair = new RequestPair(request, listener);
+ RequestContext requestContext = new RequestContext(request, listeners);
if (client.isRunning())
{
- if (requests.offer(requestPair))
+ if (requests.offer(requestContext))
{
- if (!client.isRunning() && requests.remove(requestPair))
+ if (!client.isRunning() && requests.remove(requestContext))
{
throw new RejectedExecutionException(client + " is stopping");
}
@@ -202,15 +202,15 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private void drain(Throwable x)
{
- RequestPair pair;
- while ((pair = requests.poll()) != null)
+ RequestContext requestContext;
+ while ((requestContext = requests.poll()) != null)
{
- Request request = pair.request;
+ Request request = requestContext.request;
requestNotifier.notifyFailure(request, x);
- Response.Listener listener = pair.listener;
- HttpResponse response = new HttpResponse(request, listener);
- responseNotifier.notifyFailure(listener, response, x);
- responseNotifier.notifyComplete(listener, new Result(request, x, response, x));
+ List listeners = requestContext.listeners;
+ HttpResponse response = new HttpResponse(request, listeners);
+ responseNotifier.notifyFailure(listeners, response, x);
+ responseNotifier.notifyComplete(listeners, new Result(request, x, response, x));
}
}
@@ -223,37 +223,40 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
*
* @param connection the new connection
*/
- protected void process(final Connection connection, boolean dispatch)
+ protected void process(Connection connection, boolean dispatch)
{
- RequestPair requestPair = requests.poll();
- if (requestPair == null)
+ // Ugly cast, but lack of generic reification forces it
+ final HttpConnection httpConnection = (HttpConnection)connection;
+
+ RequestContext requestContext = requests.poll();
+ if (requestContext == null)
{
- LOG.debug("{} idle", connection);
- if (!idleConnections.offer(connection))
+ LOG.debug("{} idle", httpConnection);
+ if (!idleConnections.offer(httpConnection))
{
LOG.debug("{} idle overflow");
- connection.close();
+ httpConnection.close();
}
if (!client.isRunning())
{
LOG.debug("{} is stopping", client);
- remove(connection);
- connection.close();
+ remove(httpConnection);
+ httpConnection.close();
}
}
else
{
- final Request request = requestPair.request;
- final Response.Listener listener = requestPair.listener;
- if (request.aborted())
+ final Request request = requestContext.request;
+ final List listeners = requestContext.listeners;
+ if (request.isAborted())
{
- abort(request, listener, "Aborted");
+ abort(request, listeners, "Aborted");
LOG.debug("Aborted {} before processing", request);
}
else
{
- LOG.debug("{} active", connection);
- if (!activeConnections.offer(connection))
+ LOG.debug("{} active", httpConnection);
+ if (!activeConnections.offer(httpConnection))
{
LOG.warn("{} active overflow");
}
@@ -264,13 +267,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override
public void run()
{
- connection.send(request, listener);
+ httpConnection.send(request, listeners);
}
});
}
else
{
- connection.send(request, listener);
+ httpConnection.send(request, listeners);
}
}
}
@@ -333,14 +336,14 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
public boolean abort(Request request, String reason)
{
- for (RequestPair pair : requests)
+ for (RequestContext requestContext : requests)
{
- if (pair.request == request)
+ if (requestContext.request == request)
{
- if (requests.remove(pair))
+ if (requests.remove(requestContext))
{
// We were able to remove the pair, so it won't be processed
- abort(request, pair.listener, reason);
+ abort(request, requestContext.listeners, reason);
LOG.debug("Aborted {} while queued", request);
return true;
}
@@ -349,13 +352,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return false;
}
- private void abort(Request request, Response.Listener listener, String reason)
+ private void abort(Request request, List listeners, String reason)
{
- HttpResponse response = new HttpResponse(request, listener);
+ HttpResponse response = new HttpResponse(request, listeners);
HttpResponseException responseFailure = new HttpResponseException(reason, response);
- responseNotifier.notifyFailure(listener, response, responseFailure);
+ responseNotifier.notifyFailure(listeners, response, responseFailure);
HttpRequestException requestFailure = new HttpRequestException(reason, request);
- responseNotifier.notifyComplete(listener, new Result(request, requestFailure, response, responseFailure));
+ responseNotifier.notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
}
@Override
@@ -382,15 +385,15 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return String.format("%s(%s://%s:%d)", HttpDestination.class.getSimpleName(), getScheme(), getHost(), getPort());
}
- private static class RequestPair
+ private static class RequestContext
{
private final Request request;
- private final Response.Listener listener;
+ private final List listeners;
- private RequestPair(Request request, Response.Listener listener)
+ private RequestContext(Request request, List listeners)
{
this.request = request;
- this.listener = listener;
+ this.listeners = listeners;
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
index 50ebd35e9a2..8abcd3d2201 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.client;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicMarkableReference;
@@ -37,18 +38,19 @@ public class HttpExchange
private final HttpConversation conversation;
private final HttpConnection connection;
private final Request request;
- private final Response.Listener listener;
+ private final List listeners;
private final HttpResponse response;
+ private volatile boolean last;
private volatile Throwable requestFailure;
private volatile Throwable responseFailure;
- public HttpExchange(HttpConversation conversation, HttpConnection connection, Request request, Response.Listener listener)
+ public HttpExchange(HttpConversation conversation, HttpConnection connection, Request request, List listeners)
{
this.conversation = conversation;
this.connection = connection;
this.request = request;
- this.listener = listener;
- this.response = new HttpResponse(request, listener);
+ this.listeners = listeners;
+ this.response = new HttpResponse(request, listeners);
}
public HttpConversation getConversation()
@@ -66,9 +68,9 @@ public class HttpExchange
return requestFailure;
}
- public Response.Listener getResponseListener()
+ public List getResponseListeners()
{
- return listener;
+ return listeners;
}
public HttpResponse getResponse()
@@ -81,6 +83,22 @@ public class HttpExchange
return responseFailure;
}
+ /**
+ * @return whether this exchange is the last in the conversation
+ */
+ public boolean isLast()
+ {
+ return last;
+ }
+
+ /**
+ * @param last whether this exchange is the last in the conversation
+ */
+ public void setLast(boolean last)
+ {
+ this.last = last;
+ }
+
public void receive()
{
connection.receive();
@@ -159,12 +177,13 @@ public class HttpExchange
{
// Request and response completed
LOG.debug("{} complete", this);
- if (conversation.getLastExchange() == this)
+ if (isLast())
{
HttpExchange first = conversation.getExchanges().peekFirst();
- Response.Listener listener = first.getResponseListener();
- if (listener instanceof ResponseListener.Timed)
- ((ResponseListener.Timed)listener).cancel();
+ List listeners = first.getResponseListeners();
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Schedulable)
+ ((Schedulable)listener).cancel();
conversation.complete();
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index 2a00894edd0..e8cdec6d1db 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -20,8 +20,11 @@ package org.eclipse.jetty.client;
import java.io.EOFException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
+import java.util.Locale;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
@@ -139,30 +142,41 @@ public class HttpReceiver implements HttpParser.ResponseHandler
response.version(version).status(status).reason(reason);
// Probe the protocol handlers
- Response.Listener currentListener = exchange.getResponseListener();
- Response.Listener initialListener = conversation.getExchanges().peekFirst().getResponseListener();
+ HttpExchange initialExchange = conversation.getExchanges().peekFirst();
HttpClient client = connection.getHttpClient();
ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
Response.Listener handlerListener = protocolHandler == null ? null : protocolHandler.getResponseListener();
if (handlerListener == null)
{
- conversation.setLastExchange(exchange);
- if (currentListener == initialListener)
- conversation.setResponseListener(initialListener);
+ exchange.setLast(true);
+ if (initialExchange == exchange)
+ {
+ conversation.setResponseListeners(exchange.getResponseListeners());
+ }
else
- conversation.setResponseListener(new DoubleResponseListener(responseNotifier, currentListener, initialListener));
+ {
+ List listeners = new ArrayList<>(exchange.getResponseListeners());
+ listeners.addAll(initialExchange.getResponseListeners());
+ conversation.setResponseListeners(listeners);
+ }
}
else
{
LOG.debug("Found protocol handler {}", protocolHandler);
- if (currentListener == initialListener)
- conversation.setResponseListener(handlerListener);
+ if (initialExchange == exchange)
+ {
+ conversation.setResponseListeners(Collections.singletonList(handlerListener));
+ }
else
- conversation.setResponseListener(new DoubleResponseListener(responseNotifier, currentListener, handlerListener));
+ {
+ List listeners = new ArrayList<>(exchange.getResponseListeners());
+ listeners.add(handlerListener);
+ conversation.setResponseListeners(listeners);
+ }
}
LOG.debug("Receiving {}", response);
- responseNotifier.notifyBegin(conversation.getResponseListener(), response);
+ responseNotifier.notifyBegin(conversation.getResponseListeners(), response);
}
}
return false;
@@ -178,7 +192,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
if (exchange != null)
{
exchange.getResponse().getHeaders().add(name, value);
- switch (name.toLowerCase())
+ switch (name.toLowerCase(Locale.ENGLISH))
{
case "set-cookie":
case "set-cookie2":
@@ -212,7 +226,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
HttpConversation conversation = exchange.getConversation();
HttpResponse response = exchange.getResponse();
LOG.debug("Headers {}", response);
- responseNotifier.notifyHeaders(conversation.getResponseListener(), response);
+ responseNotifier.notifyHeaders(conversation.getResponseListeners(), response);
Enumeration contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
if (contentEncodings != null)
@@ -254,7 +268,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
LOG.debug("{} {}: {} bytes", decoder, response, buffer.remaining());
}
- responseNotifier.notifyContent(conversation.getResponseListener(), response, buffer);
+ responseNotifier.notifyContent(conversation.getResponseListeners(), response, buffer);
}
}
return false;
@@ -287,8 +301,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler
exchange.terminateResponse();
HttpResponse response = exchange.getResponse();
- Response.Listener listener = exchange.getConversation().getResponseListener();
- responseNotifier.notifySuccess(listener, response);
+ List listeners = exchange.getConversation().getResponseListeners();
+ responseNotifier.notifySuccess(listeners, response);
LOG.debug("Received {}", response);
Result result = completion.getReference();
@@ -296,7 +310,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
{
connection.complete(exchange, !result.isFailed());
- responseNotifier.notifyComplete(listener, result);
+ responseNotifier.notifyComplete(listeners, result);
}
return true;
@@ -330,7 +344,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
HttpResponse response = exchange.getResponse();
HttpConversation conversation = exchange.getConversation();
- responseNotifier.notifyFailure(conversation.getResponseListener(), response, failure);
+ responseNotifier.notifyFailure(conversation.getResponseListeners(), response, failure);
LOG.debug("Failed {} {}", response, failure);
Result result = completion.getReference();
@@ -338,7 +352,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler
{
connection.complete(exchange, false);
- responseNotifier.notifyComplete(conversation.getResponseListener(), result);
+ responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
}
return true;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index d6baccf9b20..537c69966b3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -50,7 +50,8 @@ public class HttpRequest implements Request
private final HttpFields headers = new HttpFields();
private final Fields params = new Fields();
private final Map attributes = new HashMap<>();
- private final List listeners = new ArrayList<>();
+ private final List requestListeners = new ArrayList<>();
+ private final List responseListeners = new ArrayList<>();
private final HttpClient client;
private final long conversation;
private final String host;
@@ -240,15 +241,89 @@ public class HttpRequest implements Request
}
@Override
- public List getListeners()
+ public List getRequestListeners(Class type)
{
- return listeners;
+ ArrayList result = new ArrayList<>();
+ for (RequestListener listener : requestListeners)
+ if (type == null || type.isInstance(listener))
+ result.add((T)listener);
+ return result;
}
@Override
public Request listener(Request.Listener listener)
{
- this.listeners.add(listener);
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onRequestQueued(QueuedListener listener)
+ {
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onRequestBegin(BeginListener listener)
+ {
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onRequestHeaders(HeadersListener listener)
+ {
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onRequestSuccess(SuccessListener listener)
+ {
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onRequestFailure(FailureListener listener)
+ {
+ this.requestListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onResponseBegin(Response.BeginListener listener)
+ {
+ this.responseListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onResponseHeaders(Response.HeadersListener listener)
+ {
+ this.responseListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onResponseContent(Response.ContentListener listener)
+ {
+ this.responseListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onResponseSuccess(Response.SuccessListener listener)
+ {
+ this.responseListeners.add(listener);
+ return this;
+ }
+
+ @Override
+ public Request onResponseFailure(Response.FailureListener listener)
+ {
+ this.responseListeners.add(listener);
return this;
}
@@ -320,9 +395,11 @@ public class HttpRequest implements Request
}
@Override
- public void send(final Response.Listener listener)
+ public void send(Response.CompleteListener listener)
{
- client.send(this, listener);
+ if (listener != null)
+ responseListeners.add(listener);
+ client.send(this, responseListeners);
}
@Override
@@ -331,12 +408,12 @@ public class HttpRequest implements Request
aborted = true;
if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, reason))
return true;
- HttpConversation conversation = client.getConversation(getConversationID());
- return conversation.abort(reason);
+ HttpConversation conversation = client.getConversation(getConversationID(), false);
+ return conversation != null && conversation.abort(reason);
}
@Override
- public boolean aborted()
+ public boolean isAborted()
{
return aborted;
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
index 62d1f7a845c..0b49f3be92e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.client;
+import java.util.ArrayList;
+import java.util.List;
+
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
@@ -27,15 +30,15 @@ public class HttpResponse implements Response
{
private final HttpFields headers = new HttpFields();
private final Request request;
- private final Listener listener;
+ private final List listeners;
private HttpVersion version;
private int status;
private String reason;
- public HttpResponse(Request request, Listener listener)
+ public HttpResponse(Request request, List listeners)
{
this.request = request;
- this.listener = listener;
+ this.listeners = listeners;
}
public HttpVersion getVersion()
@@ -85,9 +88,13 @@ public class HttpResponse implements Response
}
@Override
- public Listener getListener()
+ public List getListeners(Class type)
{
- return listener;
+ ArrayList result = new ArrayList<>();
+ for (ResponseListener listener : listeners)
+ if (type == null || type.isInstance(listener))
+ result.add((T)listener);
+ return result;
}
@Override
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index a301bc053da..dabd10cbc12 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -19,8 +19,10 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicMarkableReference;
@@ -67,15 +69,20 @@ public class HttpSender
// Arrange the listeners, so that if there is a request failure the proper listeners are notified
HttpConversation conversation = exchange.getConversation();
- Response.Listener currentListener = exchange.getResponseListener();
- Response.Listener initialListener = conversation.getExchanges().peekFirst().getResponseListener();
- if (initialListener == currentListener)
- conversation.setResponseListener(initialListener);
+ HttpExchange initialExchange = conversation.getExchanges().peekFirst();
+ if (initialExchange == exchange)
+ {
+ conversation.setResponseListeners(exchange.getResponseListeners());
+ }
else
- conversation.setResponseListener(new DoubleResponseListener(responseNotifier, currentListener, initialListener));
+ {
+ List listeners = new ArrayList<>(exchange.getResponseListeners());
+ listeners.addAll(initialExchange.getResponseListeners());
+ conversation.setResponseListeners(listeners);
+ }
Request request = exchange.getRequest();
- if (request.aborted())
+ if (request.isAborted())
{
exchange.abort(null);
}
@@ -123,7 +130,7 @@ public class HttpSender
return;
final Request request = exchange.getRequest();
- HttpConversation conversation = client.getConversation(request.getConversationID());
+ HttpConversation conversation = exchange.getConversation();
HttpGenerator.RequestInfo requestInfo = null;
boolean expect100 = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
@@ -359,7 +366,7 @@ public class HttpSender
connection.complete(exchange, !result.isFailed());
HttpConversation conversation = exchange.getConversation();
- responseNotifier.notifyComplete(conversation.getResponseListener(), result);
+ responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
}
return true;
@@ -393,7 +400,7 @@ public class HttpSender
Result result = completion.getReference();
boolean notCommitted = current == State.IDLE || current == State.SEND;
- if (result == null && notCommitted && !request.aborted())
+ if (result == null && notCommitted && !request.isAborted())
{
result = exchange.responseComplete(failure).getReference();
exchange.terminateResponse();
@@ -405,7 +412,7 @@ public class HttpSender
connection.complete(exchange, false);
HttpConversation conversation = exchange.getConversation();
- responseNotifier.notifyComplete(conversation.getResponseListener(), result);
+ responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
}
return true;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
index a19f428ea1d..c53b29e4be8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
@@ -18,10 +18,11 @@
package org.eclipse.jetty.client;
+import java.util.List;
+
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler
@@ -105,7 +106,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
private void redirect(Result result, HttpMethod method, String location)
{
final Request request = result.getRequest();
- HttpConversation conversation = client.getConversation(request.getConversationID());
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
if (redirects == null)
redirects = 0;
@@ -115,31 +116,22 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
++redirects;
conversation.setAttribute(ATTRIBUTE, redirects);
- Request redirect = client.newRequest(request.getConversationID(), location);
+ Request redirect = client.copyRequest(request, location);
// Use given method
redirect.method(method);
- redirect.version(request.getVersion());
-
- // Copy headers
- for (HttpFields.Field header : request.getHeaders())
- redirect.header(header.getName(), header.getValue());
-
- // Copy content
- redirect.content(request.getContent());
-
- redirect.listener(new Request.Listener.Empty()
+ redirect.onRequestBegin(new Request.BeginListener()
{
@Override
public void onBegin(Request redirect)
{
- if (request.aborted())
+ if (request.isAborted())
redirect.abort(null);
}
});
- redirect.send(new Response.Listener.Empty());
+ redirect.send(null);
}
else
{
@@ -151,10 +143,10 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
Request request = result.getRequest();
Response response = result.getResponse();
- HttpConversation conversation = client.getConversation(request.getConversationID());
- Response.Listener listener = conversation.getExchanges().peekFirst().getResponseListener();
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ List listeners = conversation.getExchanges().peekFirst().getResponseListeners();
// TODO: should we replay all events, or just the failure ?
- notifier.notifyFailure(listener, response, failure);
- notifier.notifyComplete(listener, new Result(request, response, failure));
+ notifier.notifyFailure(listeners, response, failure);
+ notifier.notifyComplete(listeners, new Result(request, response, failure));
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
index 34518697178..b04949b4852 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
@@ -35,13 +35,13 @@ public class RequestNotifier
public void notifyQueued(Request request)
{
- for (Request.Listener listener : request.getListeners())
+ for (Request.QueuedListener listener : request.getRequestListeners(Request.QueuedListener.class))
notifyQueued(listener, request);
for (Request.Listener listener : client.getRequestListeners())
notifyQueued(listener, request);
}
- private void notifyQueued(Request.Listener listener, Request request)
+ private void notifyQueued(Request.QueuedListener listener, Request request)
{
try
{
@@ -56,13 +56,13 @@ public class RequestNotifier
public void notifyBegin(Request request)
{
- for (Request.Listener listener : request.getListeners())
+ for (Request.BeginListener listener : request.getRequestListeners(Request.BeginListener.class))
notifyBegin(listener, request);
for (Request.Listener listener : client.getRequestListeners())
notifyBegin(listener, request);
}
- private void notifyBegin(Request.Listener listener, Request request)
+ private void notifyBegin(Request.BeginListener listener, Request request)
{
try
{
@@ -77,13 +77,13 @@ public class RequestNotifier
public void notifyHeaders(Request request)
{
- for (Request.Listener listener : request.getListeners())
+ for (Request.HeadersListener listener : request.getRequestListeners(Request.HeadersListener.class))
notifyHeaders(listener, request);
for (Request.Listener listener : client.getRequestListeners())
notifyHeaders(listener, request);
}
- private void notifyHeaders(Request.Listener listener, Request request)
+ private void notifyHeaders(Request.HeadersListener listener, Request request)
{
try
{
@@ -98,13 +98,13 @@ public class RequestNotifier
public void notifySuccess(Request request)
{
- for (Request.Listener listener : request.getListeners())
+ for (Request.SuccessListener listener : request.getRequestListeners(Request.SuccessListener.class))
notifySuccess(listener, request);
for (Request.Listener listener : client.getRequestListeners())
notifySuccess(listener, request);
}
- private void notifySuccess(Request.Listener listener, Request request)
+ private void notifySuccess(Request.SuccessListener listener, Request request)
{
try
{
@@ -119,13 +119,13 @@ public class RequestNotifier
public void notifyFailure(Request request, Throwable failure)
{
- for (Request.Listener listener : request.getListeners())
+ for (Request.FailureListener listener : request.getRequestListeners(Request.FailureListener.class))
notifyFailure(listener, request, failure);
for (Request.Listener listener : client.getRequestListeners())
notifyFailure(listener, request, failure);
}
- private void notifyFailure(Request.Listener listener, Request request, Throwable failure)
+ private void notifyFailure(Request.FailureListener listener, Request request, Throwable failure)
{
try
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
index 7c7d3304b7f..a1b64c6d2ee 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
+import java.util.List;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
@@ -37,12 +38,18 @@ public class ResponseNotifier
this.client = client;
}
- public void notifyBegin(Response.Listener listener, Response response)
+ public void notifyBegin(List listeners, Response response)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.BeginListener)
+ notifyBegin((Response.BeginListener)listener, response);
+ }
+
+ private void notifyBegin(Response.BeginListener listener, Response response)
{
try
{
- if (listener != null)
- listener.onBegin(response);
+ listener.onBegin(response);
}
catch (Exception x)
{
@@ -50,12 +57,18 @@ public class ResponseNotifier
}
}
- public void notifyHeaders(Response.Listener listener, Response response)
+ public void notifyHeaders(List listeners, Response response)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.HeadersListener)
+ notifyHeaders((Response.HeadersListener)listener, response);
+ }
+
+ private void notifyHeaders(Response.HeadersListener listener, Response response)
{
try
{
- if (listener != null)
- listener.onHeaders(response);
+ listener.onHeaders(response);
}
catch (Exception x)
{
@@ -63,12 +76,19 @@ public class ResponseNotifier
}
}
- public void notifyContent(Response.Listener listener, Response response, ByteBuffer buffer)
+ public void notifyContent(List listeners, Response response, ByteBuffer buffer)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.ContentListener)
+ notifyContent((Response.ContentListener)listener, response, buffer);
+
+ }
+
+ private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer)
{
try
{
- if (listener != null)
- listener.onContent(response, buffer);
+ listener.onContent(response, buffer);
}
catch (Exception x)
{
@@ -76,12 +96,18 @@ public class ResponseNotifier
}
}
- public void notifySuccess(Response.Listener listener, Response response)
+ public void notifySuccess(List listeners, Response response)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.SuccessListener)
+ notifySuccess((Response.SuccessListener)listener, response);
+ }
+
+ private void notifySuccess(Response.SuccessListener listener, Response response)
{
try
{
- if (listener != null)
- listener.onSuccess(response);
+ listener.onSuccess(response);
}
catch (Exception x)
{
@@ -89,12 +115,18 @@ public class ResponseNotifier
}
}
- public void notifyFailure(Response.Listener listener, Response response, Throwable failure)
+ public void notifyFailure(List listeners, Response response, Throwable failure)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.FailureListener)
+ notifyFailure((Response.FailureListener)listener, response, failure);
+ }
+
+ private void notifyFailure(Response.FailureListener listener, Response response, Throwable failure)
{
try
{
- if (listener != null)
- listener.onFailure(response, failure);
+ listener.onFailure(response, failure);
}
catch (Exception x)
{
@@ -102,12 +134,18 @@ public class ResponseNotifier
}
}
- public void notifyComplete(Response.Listener listener, Result result)
+ public void notifyComplete(List listeners, Result result)
+ {
+ for (Response.ResponseListener listener : listeners)
+ if (listener instanceof Response.CompleteListener)
+ notifyComplete((Response.CompleteListener)listener, result);
+ }
+
+ private void notifyComplete(Response.CompleteListener listener, Result result)
{
try
{
- if (listener != null)
- listener.onComplete(result);
+ listener.onComplete(result);
}
catch (Exception x)
{
@@ -115,37 +153,37 @@ public class ResponseNotifier
}
}
- public void forwardSuccess(Response.Listener listener, Response response)
+ public void forwardSuccess(List listeners, Response response)
{
- notifyBegin(listener, response);
- notifyHeaders(listener, response);
+ notifyBegin(listeners, response);
+ notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
- notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
- notifySuccess(listener, response);
+ notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
+ notifySuccess(listeners, response);
}
- public void forwardSuccessComplete(Response.Listener listener, Request request, Response response)
+ public void forwardSuccessComplete(List listeners, Request request, Response response)
{
- HttpConversation conversation = client.getConversation(request.getConversationID());
- forwardSuccess(listener, response);
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ forwardSuccess(listeners, response);
conversation.complete();
- notifyComplete(listener, new Result(request, response));
+ notifyComplete(listeners, new Result(request, response));
}
- public void forwardFailure(Response.Listener listener, Response response, Throwable failure)
+ public void forwardFailure(List listeners, Response response, Throwable failure)
{
- notifyBegin(listener, response);
- notifyHeaders(listener, response);
+ notifyBegin(listeners, response);
+ notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
- notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
- notifyFailure(listener, response, failure);
+ notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
+ notifyFailure(listeners, response, failure);
}
- public void forwardFailureComplete(Response.Listener listener, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
+ public void forwardFailureComplete(List listeners, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
{
- HttpConversation conversation = client.getConversation(request.getConversationID());
- forwardFailure(listener, response, responseFailure);
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ forwardFailure(listeners, response, responseFailure);
conversation.complete();
- notifyComplete(listener, new Result(request, requestFailure, response, responseFailure));
+ notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Schedulable.java
similarity index 77%
rename from jetty-client/src/main/java/org/eclipse/jetty/client/ResponseListener.java
rename to jetty-client/src/main/java/org/eclipse/jetty/client/Schedulable.java
index dd61fb0bffd..cc97c4aa2fd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Schedulable.java
@@ -18,15 +18,11 @@
package org.eclipse.jetty.client;
-import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.util.thread.Scheduler;
-public interface ResponseListener extends Response.Listener
+public interface Schedulable
{
- public interface Timed extends Response.Listener
- {
- public boolean schedule(Scheduler scheduler);
+ public boolean schedule(Scheduler scheduler);
- public boolean cancel();
- }
+ public boolean cancel();
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
index 23d963ca5c8..64b719447e2 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
@@ -37,7 +37,7 @@ public interface Connection extends AutoCloseable
* @param request the request to send
* @param listener the response listener
*/
- void send(Request request, Response.Listener listener);
+ void send(Request request, Response.CompleteListener listener);
@Override
void close();
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index b1356b08149..1d36b9e8894 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -20,12 +20,12 @@ package org.eclipse.jetty.client.api;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
@@ -207,16 +207,77 @@ public interface Request
Request followRedirects(boolean follow);
/**
- * @return the listener for request events
+ * @param listenerClass the class of the listener, or null for all listeners classes
+ * @return the listeners for request events of the given class
*/
- List getListeners();
+ List getRequestListeners(Class listenerClass);
/**
- * @param listener the listener for request events
+ * @param listener a listener for request events
* @return this request object
*/
Request listener(Listener listener);
+ /**
+ * @param listener a listener for request queued event
+ * @return this request object
+ */
+ Request onRequestQueued(QueuedListener listener);
+
+ /**
+ * @param listener a listener for request begin event
+ * @return this request object
+ */
+ Request onRequestBegin(BeginListener listener);
+
+ /**
+ * @param listener a listener for request headers event
+ * @return this request object
+ */
+ Request onRequestHeaders(HeadersListener listener);
+
+ /**
+ * @param listener a listener for request success event
+ * @return this request object
+ */
+ Request onRequestSuccess(SuccessListener listener);
+
+ /**
+ * @param listener a listener for request failure event
+ * @return this request object
+ */
+ Request onRequestFailure(FailureListener listener);
+
+ /**
+ * @param listener a listener for response begin event
+ * @return this request object
+ */
+ Request onResponseBegin(Response.BeginListener listener);
+
+ /**
+ * @param listener a listener for response headers event
+ * @return this request object
+ */
+ Request onResponseHeaders(Response.HeadersListener listener);
+
+ /**
+ * @param listener a listener for response content events
+ * @return this request object
+ */
+ Request onResponseContent(Response.ContentListener listener);
+
+ /**
+ * @param listener a listener for response success event
+ * @return this request object
+ */
+ Request onResponseSuccess(Response.SuccessListener listener);
+
+ /**
+ * @param listener a listener for response failure event
+ * @return this request object
+ */
+ Request onResponseFailure(Response.FailureListener listener);
+
/**
* Sends this request and returns a {@link Future} that can be used to wait for the
* request and the response to be completed (either with a success or a failure).
@@ -242,7 +303,7 @@ public interface Request
*
* @param listener the listener that receives response events
*/
- void send(Response.Listener listener);
+ void send(Response.CompleteListener listener);
/**
* Attempts to abort the send of this request.
@@ -255,12 +316,13 @@ public interface Request
/**
* @return whether {@link #abort(String)} was called
*/
- boolean aborted();
+ boolean isAborted();
- /**
- * Listener for request events
- */
- public interface Listener
+ public interface RequestListener extends EventListener
+ {
+ }
+
+ public interface QueuedListener extends RequestListener
{
/**
* Callback method invoked when the request is queued, waiting to be sent
@@ -268,7 +330,10 @@ public interface Request
* @param request the request being queued
*/
public void onQueued(Request request);
+ }
+ public interface BeginListener extends RequestListener
+ {
/**
* Callback method invoked when the request begins being processed in order to be sent.
* This is the last opportunity to modify the request.
@@ -276,7 +341,10 @@ public interface Request
* @param request the request that begins being processed
*/
public void onBegin(Request request);
+ }
+ public interface HeadersListener extends RequestListener
+ {
/**
* Callback method invoked when the request headers (and perhaps small content) have been sent.
* The request is now committed, and in transit to the server, and further modifications to the
@@ -284,21 +352,33 @@ public interface Request
* @param request the request that has been committed
*/
public void onHeaders(Request request);
+ }
+ public interface SuccessListener extends RequestListener
+ {
/**
* Callback method invoked when the request has been successfully sent.
*
* @param request the request sent
*/
public void onSuccess(Request request);
+ }
+ public interface FailureListener extends RequestListener
+ {
/**
* Callback method invoked when the request has failed to be sent
* @param request the request that failed
* @param failure the failure
*/
public void onFailure(Request request, Throwable failure);
+ }
+ /**
+ * Listener for all request events
+ */
+ public interface Listener extends QueuedListener, BeginListener, HeadersListener, SuccessListener, FailureListener
+ {
/**
* An empty implementation of {@link Listener}
*/
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
index 72c97c7c741..951d4853f66 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
@@ -19,6 +19,8 @@
package org.eclipse.jetty.client.api;
import java.nio.ByteBuffer;
+import java.util.EventListener;
+import java.util.List;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpFields;
@@ -42,9 +44,9 @@ public interface Response
long getConversationID();
/**
- * @return the response listener passed to {@link Request#send(Listener)}
+ * @return the response listener passed to {@link Request#send(CompleteListener)}
*/
- Listener getListener();
+ List getListeners(Class listenerClass);
/**
* @return the HTTP version of this response, such as "HTTP/1.1"
@@ -74,10 +76,11 @@ public interface Response
*/
boolean abort(String reason);
- /**
- * Listener for response events
- */
- public interface Listener
+ public interface ResponseListener extends EventListener
+ {
+ }
+
+ public interface BeginListener extends ResponseListener
{
/**
* Callback method invoked when the response line containing HTTP version,
@@ -88,14 +91,20 @@ public interface Response
* @param response the response containing the response line data
*/
public void onBegin(Response response);
+ }
+ public interface HeadersListener extends ResponseListener
+ {
/**
* Callback method invoked when the response headers have been received and parsed.
*
* @param response the response containing the response line data and the headers
*/
public void onHeaders(Response response);
+ }
+ public interface ContentListener extends ResponseListener
+ {
/**
* Callback method invoked when the response content has been received.
* This method may be invoked multiple times, and the {@code content} buffer must be consumed
@@ -105,14 +114,20 @@ public interface Response
* @param content the content bytes received
*/
public void onContent(Response response, ByteBuffer content);
+ }
+ public interface SuccessListener extends ResponseListener
+ {
/**
* Callback method invoked when the whole response has been successfully received.
*
* @param response the response containing the response line data and the headers
*/
public void onSuccess(Response response);
+ }
+ public interface FailureListener extends ResponseListener
+ {
/**
* Callback method invoked when the response has failed in the process of being received
*
@@ -120,7 +135,10 @@ public interface Response
* @param failure the failure happened
*/
public void onFailure(Response response, Throwable failure);
+ }
+ public interface CompleteListener extends ResponseListener
+ {
/**
* Callback method invoked when the request and the response have been processed,
* either successfully or not.
@@ -129,13 +147,20 @@ public interface Response
*
* Requests may complete after response, for example in case of big uploads that are
* discarded or read asynchronously by the server.
- * This method is always invoked after {@link #onSuccess(Response)} or
- * {@link #onFailure(Response, Throwable)}, and only when request indicates that it is completed.
+ * This method is always invoked after {@link SuccessListener#onSuccess(Response)} or
+ * {@link FailureListener#onFailure(Response, Throwable)}, and only when request indicates that
+ * it is completed.
*
* @param result the result of the request / response exchange
*/
public void onComplete(Result result);
+ }
+ /**
+ * Listener for response events
+ */
+ public interface Listener extends BeginListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener
+ {
/**
* An empty implementation of {@link Listener}
*/
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java
index 12ec25d6a68..bef0fbf305e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java
@@ -46,7 +46,6 @@ public class BlockingResponseListener extends BufferingResponseListener implemen
@Override
public void onComplete(Result result)
{
- super.onComplete(result);
response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
failure = result.getFailure();
latch.countDown();
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
index 1c4a7aa5e49..cecd461cfea 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client.util;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Locale;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
@@ -33,7 +34,7 @@ import org.eclipse.jetty.http.HttpHeader;
*
The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)}
* via {@link #getContent()} or {@link #getContentAsString()}.
*/
-public class BufferingResponseListener extends Response.Listener.Empty
+public abstract class BufferingResponseListener extends Response.Listener.Empty
{
private final int maxLength;
private volatile byte[] buffer = new byte[0];
@@ -69,7 +70,7 @@ public class BufferingResponseListener extends Response.Listener.Empty
if (contentType != null)
{
String charset = "charset=";
- int index = contentType.toLowerCase().indexOf(charset);
+ int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
if (index > 0)
{
String encoding = contentType.substring(index + charset.length());
@@ -95,6 +96,9 @@ public class BufferingResponseListener extends Response.Listener.Empty
buffer = newBuffer;
}
+ @Override
+ public abstract void onComplete(Result result);
+
public String getEncoding()
{
return encoding;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
index 066520c1bb1..63ccdb74b3a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
@@ -108,7 +109,7 @@ public class DigestAuthentication implements Authentication
Matcher matcher = PARAM_PATTERN.matcher(part);
if (matcher.matches())
{
- String name = matcher.group(1).trim().toLowerCase();
+ String name = matcher.group(1).trim().toLowerCase(Locale.ENGLISH);
String value = matcher.group(2).trim();
if (value.startsWith("\"") && value.endsWith("\""))
value = value.substring(1, value.length() - 1);
@@ -251,7 +252,7 @@ public class DigestAuthentication implements Authentication
private String nextNonceCount()
{
String padding = "00000000";
- String next = Integer.toHexString(nonceCount.incrementAndGet()).toLowerCase();
+ String next = Integer.toHexString(nonceCount.incrementAndGet()).toLowerCase(Locale.ENGLISH);
return padding.substring(0, padding.length() - next.length()) + next;
}
@@ -265,7 +266,7 @@ public class DigestAuthentication implements Authentication
private String toHexString(byte[] bytes)
{
- return TypeUtil.toHexString(bytes).toLowerCase();
+ return TypeUtil.toHexString(bytes).toLowerCase(Locale.ENGLISH);
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
index 693feebedcb..6f2cee60697 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/TimedResponseListener.java
@@ -22,7 +22,7 @@ import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-import org.eclipse.jetty.client.ResponseListener;
+import org.eclipse.jetty.client.Schedulable;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
@@ -30,7 +30,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
-public class TimedResponseListener implements ResponseListener.Timed, Runnable
+public class TimedResponseListener implements Response.Listener, Schedulable, Runnable
{
private static final Logger LOG = Log.getLogger(TimedResponseListener.class);
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
new file mode 100644
index 00000000000..e21b73f5f13
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
@@ -0,0 +1,85 @@
+//
+// ========================================================================
+// 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.client;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ExternalSiteTest
+{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
+ private HttpClient client;
+
+ @Before
+ public void prepare() throws Exception
+ {
+ client = new HttpClient();
+ client.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ client.stop();
+ }
+
+ @Test
+ public void testExternalSite() throws Exception
+ {
+ String host = "wikipedia.org";
+ int port = 80;
+
+ // Verify that we have connectivity
+ try
+ {
+ new Socket(host, port);
+ }
+ catch (IOException x)
+ {
+ Assume.assumeNoException(x);
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest(host, port).send(new Response.CompleteListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ if (!result.isFailed() && result.getResponse().getStatus() == 200)
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
index d70a2ee4907..ca90aee585e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
@@ -25,6 +25,8 @@ import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -33,6 +35,9 @@ import static org.junit.Assert.assertTrue;
public class GZIPContentDecoderTest
{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
@Test
public void testStreamNoBlocks() throws Exception
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
index 4358138efc5..ac4eac505ac 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -99,14 +99,16 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
public void test_BasicAuthentication() throws Exception
{
startBasic(new EmptyServerHandler());
- test_Authentication(new BasicAuthentication(scheme + "://localhost:" + connector.getLocalPort(), realm, "basic", "basic"));
+ String uri = scheme + "://localhost:" + connector.getLocalPort();
+ test_Authentication(new BasicAuthentication(uri, realm, "basic", "basic"));
}
@Test
public void test_DigestAuthentication() throws Exception
{
startDigest(new EmptyServerHandler());
- test_Authentication(new DigestAuthentication(scheme + "://localhost:" + connector.getLocalPort(), realm, "digest", "digest"));
+ String uri = scheme + "://localhost:" + connector.getLocalPort();
+ test_Authentication(new DigestAuthentication(uri, realm, "digest", "digest"));
}
private void test_Authentication(Authentication authentication) throws Exception
@@ -189,7 +191,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
}
});
- client.getAuthenticationStore().addAuthentication(new BasicAuthentication(scheme + "://localhost:" + connector.getLocalPort(), realm, "basic", "basic"));
+ String uri = scheme + "://localhost:" + connector.getLocalPort();
+ client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Empty requestListener = new Request.Listener.Empty()
@@ -227,7 +230,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
}
});
- client.getAuthenticationStore().addAuthentication(new BasicAuthentication(scheme + "://localhost:" + connector.getLocalPort(), realm, "basic", "basic"));
+ String uri = scheme + "://localhost:" + connector.getLocalPort();
+ client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
Request.Listener.Empty requestListener = new Request.Listener.Empty()
@@ -268,7 +272,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().add(requestListener);
AuthenticationStore authenticationStore = client.getAuthenticationStore();
- BasicAuthentication authentication = new BasicAuthentication(scheme + "://localhost:" + connector.getLocalPort(), realm, "basic", "basic");
+ String uri = scheme + "://localhost:" + connector.getLocalPort();
+ BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
authenticationStore.addAuthentication(authentication);
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
index 5604bc53595..25c4be15340 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
@@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -67,7 +68,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
client.setMaxQueueSizePerAddress(1024 * 1024);
client.setDispatchIO(false);
- Random random = new Random(1000L);
+ Random random = new Random();
int iterations = 500;
CountDownLatch latch = new CountDownLatch(iterations);
List failures = new ArrayList<>();
@@ -132,15 +133,15 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
else if (!ssl && random.nextBoolean())
request.header("X-Close", "true");
+ int contentLength = random.nextInt(maxContentLength) + 1;
switch (method)
{
case GET:
// Randomly ask the server to download data upon this GET request
if (random.nextBoolean())
- request.header("X-Download", String.valueOf(random.nextInt(maxContentLength) + 1));
+ request.header("X-Download", String.valueOf(contentLength));
break;
case POST:
- int contentLength = random.nextInt(maxContentLength) + 1;
request.header("X-Upload", String.valueOf(contentLength));
request.content(new BytesContentProvider(new byte[contentLength]));
break;
@@ -184,7 +185,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
- String method = request.getMethod().toUpperCase();
+ String method = request.getMethod().toUpperCase(Locale.ENGLISH);
switch (method)
{
case "GET":
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
index ea0cce8cce9..7f744cc22f6 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
@@ -72,7 +72,7 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.file(upload)
- .listener(new Request.Listener.Empty()
+ .onRequestSuccess(new Request.SuccessListener()
{
@Override
public void onSuccess(Request request)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index d5b27ae6b75..5ffc74a0679 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -289,7 +289,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final CountDownLatch successLatch = new CountDownLatch(2);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestBegin(new Request.BeginListener()
{
@Override
public void onBegin(Request request)
@@ -316,7 +316,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestQueued(new Request.QueuedListener()
{
@Override
public void onQueued(Request request)
@@ -371,18 +371,19 @@ public class HttpClientTest extends AbstractHttpClientServerTest
latch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .onResponseFailure(new Response.FailureListener()
{
@Override
public void onFailure(Response response, Throwable failure)
{
latch.countDown();
}
- });
+ })
+ .send(null);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .send(new Response.Listener.Empty()
+ .onResponseSuccess(new Response.SuccessListener()
{
@Override
public void onSuccess(Response response)
@@ -390,7 +391,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertEquals(200, response.getStatus());
latch.countDown();
}
- });
+ })
+ .send(null);
Assert.assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
}
@@ -419,7 +421,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.file(file)
- .listener(new Request.Listener.Empty()
+ .onRequestSuccess(new Request.SuccessListener()
{
@Override
public void onSuccess(Request request)
@@ -529,7 +531,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final int port = connector.getLocalPort();
client.newRequest(host, port)
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestBegin(new Request.BeginListener()
{
@Override
public void onBegin(Request request)
@@ -621,8 +623,4 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
-
- // TODO: add a test to idle timeout a request that is in the queue...
- // TODO: even though "idle timeout" only applies to connections
- // TODO: so do we still need a "global" timeout that takes in count queue time + send time + receive time ?
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
index 700c14e6296..ba3f6488557 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -151,7 +151,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
TimeUnit.MILLISECONDS.sleep(2 * timeout);
- Assert.assertFalse(request.aborted());
+ Assert.assertFalse(request.isAborted());
}
@Slow
@@ -208,7 +208,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
TimeUnit.MILLISECONDS.sleep(2 * timeout);
- Assert.assertFalse(request.aborted());
+ Assert.assertFalse(request.isAborted());
}
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 46e1870103e..b0aa3b6135e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -68,7 +68,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
final CountDownLatch successLatch = new CountDownLatch(3);
client.newRequest(host, port)
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestSuccess(new Request.SuccessListener()
{
@Override
public void onSuccess(Request request)
@@ -76,7 +76,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
successLatch.countDown();
}
})
- .send(new Response.Listener.Empty()
+ .onResponseHeaders(new Response.HeadersListener()
{
@Override
public void onHeaders(Response response)
@@ -85,7 +85,9 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
Assert.assertEquals(1, activeConnections.size());
headersLatch.countDown();
}
-
+ })
+ .send(new Response.Listener.Empty()
+ {
@Override
public void onSuccess(Response response)
{
@@ -308,7 +310,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
final CountDownLatch failureLatch = new CountDownLatch(2);
client.newRequest(host, port)
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestFailure(new Request.FailureListener()
{
@Override
public void onFailure(Request request, Throwable failure)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java
index 0b4c57cb020..744fd437451 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieParserTest.java
@@ -21,11 +21,16 @@ package org.eclipse.jetty.client;
import java.util.List;
import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.toolchain.test.TestTracker;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
public class HttpCookieParserTest
{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
@Test
public void testParseCookie1() throws Exception
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java
index b616dd18449..804a7656bda 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java
@@ -23,11 +23,16 @@ import java.util.List;
import org.eclipse.jetty.client.api.CookieStore;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.toolchain.test.TestTracker;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
public class HttpCookieStoreTest
{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
private HttpClient client = new HttpClient();
@Test
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
index 6618b1ea9e3..aa7948e925c 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.net.URI;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -34,13 +35,18 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.toolchain.test.TestTracker;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
public class HttpReceiverTest
{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
private HttpClient client;
private HttpDestination destination;
private ByteArrayEndPoint endPoint;
@@ -68,7 +74,7 @@ public class HttpReceiverTest
{
HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
BlockingResponseListener listener = new BlockingResponseListener(request);
- HttpExchange exchange = new HttpExchange(conversation, connection, request, listener);
+ HttpExchange exchange = new HttpExchange(conversation, connection, request, Collections.singletonList(listener));
conversation.getExchanges().offer(exchange);
connection.setExchange(exchange);
exchange.requestComplete(null);
@@ -84,7 +90,7 @@ public class HttpReceiverTest
"Content-length: 0\r\n" +
"\r\n");
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
Response response = listener.get(5, TimeUnit.SECONDS);
@@ -108,7 +114,7 @@ public class HttpReceiverTest
"\r\n" +
content);
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
Response response = listener.get(5, TimeUnit.SECONDS);
@@ -135,7 +141,7 @@ public class HttpReceiverTest
"\r\n" +
content1);
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
endPoint.setInputEOF();
exchange.receive();
@@ -159,7 +165,7 @@ public class HttpReceiverTest
"Content-length: 1\r\n" +
"\r\n");
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
// Simulate an idle timeout
connection.idleTimeout();
@@ -183,7 +189,7 @@ public class HttpReceiverTest
"Content-length: A\r\n" +
"\r\n");
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
try
@@ -214,7 +220,7 @@ public class HttpReceiverTest
"Content-Encoding: gzip\r\n" +
"\r\n");
HttpExchange exchange = newExchange();
- BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListener();
+ BlockingResponseListener listener = (BlockingResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
endPoint.reset();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
index 5e7d73ab663..337db292b2f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
@@ -139,7 +139,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestHeaders(new Request.HeadersListener()
{
@Override
public void onHeaders(Request request)
@@ -191,7 +191,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .listener(new Request.Listener.Empty()
+ .onRequestHeaders(new Request.HeadersListener()
{
@Override
public void onHeaders(Request request)
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
index 4b16b688bc5..74efb0cd14c 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
@@ -55,14 +55,16 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .send(new Response.Listener.Empty()
+ .onResponseBegin(new Response.BeginListener()
{
@Override
public void onBegin(Response response)
{
response.abort(null);
}
-
+ })
+ .send(new Response.CompleteListener()
+ {
@Override
public void onComplete(Result result)
{
@@ -81,13 +83,16 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .send(new Response.Listener.Empty()
+ .onResponseHeaders(new Response.HeadersListener()
{
@Override
public void onHeaders(Response response)
{
response.abort(null);
}
+ })
+ .send(new Response.CompleteListener()
+ {
@Override
public void onComplete(Result result)
@@ -126,14 +131,16 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .send(new Response.Listener.Empty()
+ .onResponseContent(new Response.ContentListener()
{
@Override
public void onContent(Response response, ByteBuffer content)
{
response.abort(null);
}
-
+ })
+ .send(new Response.CompleteListener()
+ {
@Override
public void onComplete(Result result)
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
index 5f8bbb65dd1..9f46eccdb76 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.net.URI;
import java.nio.ByteBuffer;
+import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -28,14 +29,19 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
public class HttpSenderTest
{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
private HttpClient client;
@Before
@@ -74,7 +80,7 @@ public class HttpSenderTest
successLatch.countDown();
}
});
- connection.send(request, null);
+ connection.send(request, (Response.CompleteListener)null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
@@ -91,7 +97,7 @@ public class HttpSenderTest
HttpDestination destination = new HttpDestination(client, "http", "localhost", 8080);
HttpConnection connection = new HttpConnection(client, endPoint, destination);
Request request = client.newRequest(URI.create("http://localhost/"));
- connection.send(request, null);
+ connection.send(request, (Response.CompleteListener)null);
// This take will free space in the buffer and allow for the write to complete
StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
@@ -202,7 +208,7 @@ public class HttpSenderTest
successLatch.countDown();
}
});
- connection.send(request, null);
+ connection.send(request, (Response.CompleteListener)null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
@@ -237,7 +243,7 @@ public class HttpSenderTest
successLatch.countDown();
}
});
- connection.send(request, null);
+ connection.send(request, (Response.CompleteListener)null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
@@ -279,12 +285,12 @@ public class HttpSenderTest
successLatch.countDown();
}
});
- connection.send(request, null);
+ connection.send(request, (Response.CompleteListener)null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
- String content = Integer.toHexString(content1.length()).toUpperCase() + "\r\n" + content1 + "\r\n";
- content += Integer.toHexString(content2.length()).toUpperCase() + "\r\n" + content2 + "\r\n";
+ String content = Integer.toHexString(content1.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content1 + "\r\n";
+ content += Integer.toHexString(content2.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content2 + "\r\n";
content += "0\r\n\r\n";
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
index 5316b500ddf..9b42604d6d3 100644
--- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml
+++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
@@ -43,6 +43,16 @@
/etc/webdefault.xml1true
+
+
+
+
+
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java
similarity index 54%
rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java
rename to jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java
index 076dafe93f6..8cb5c344e82 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java
@@ -21,10 +21,15 @@ package org.eclipse.jetty.deploy;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.resource.Resource;
/**
@@ -32,42 +37,55 @@ import org.eclipse.jetty.util.resource.Resource;
*
* Supplies properties defined in a file.
*/
-public class FileConfigurationManager implements ConfigurationManager
+@ManagedObject("Configure deployed webapps via properties")
+public class PropertiesConfigurationManager implements ConfigurationManager
{
- private Resource _file;
- private Map _map = new HashMap();
+ private String _properties;
+ private final Map _map = new HashMap();
- public FileConfigurationManager()
+ public PropertiesConfigurationManager(String properties)
+ {
+ }
+
+ public PropertiesConfigurationManager()
{
}
- public void setFile(String filename) throws MalformedURLException, IOException
+ @ManagedAttribute("A file or URL of properties")
+ public void setFile(String resource) throws MalformedURLException, IOException
{
- _file = Resource.newResource(filename);
+ _properties=resource;
+ _map.clear();
+ loadProperties(_properties);
}
+ public String getFile()
+ {
+ return _properties;
+ }
+
+ @ManagedOperation("Set a property")
+ public void put(@Name("name")String name, @Name("value")String value)
+ {
+ _map.put(name,value);
+ }
+
/**
* @see org.eclipse.jetty.deploy.ConfigurationManager#getProperties()
*/
+ @Override
public Map getProperties()
{
- try
- {
- loadProperties();
- return _map;
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
+ return new HashMap<>(_map);
}
- private void loadProperties() throws FileNotFoundException, IOException
- {
- if (_map.isEmpty() && _file!=null)
+ private void loadProperties(String resource) throws FileNotFoundException, IOException
+ {
+ Resource file=Resource.newResource(resource);
+ if (file!=null && file.exists())
{
Properties properties = new Properties();
- properties.load(_file.getInputStream());
+ properties.load(file.getInputStream());
for (Map.Entry
- *
+ *
* @goal run-forked
* @requiresDependencyResolution compile+runtime
* @execute phase="test-compile"
@@ -72,17 +85,17 @@ import org.eclipse.jetty.util.IO;
*
*/
public class JettyRunForkedMojo extends AbstractMojo
-{
+{
public String PORT_SYSPROPERTY = "jetty.port";
-
+
/**
* Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
* Use WITH CAUTION as you may wind up with duplicate jars/classes.
* @parameter default-value="false"
*/
protected boolean useProvidedScope;
-
-
+
+
/**
* The maven project.
*
@@ -91,9 +104,9 @@ public class JettyRunForkedMojo extends AbstractMojo
* @readonly
*/
private MavenProject project;
+
-
-
+
/**
* If true, the <testOutputDirectory>
* and the dependencies of <scope>test<scope>
@@ -101,27 +114,27 @@ public class JettyRunForkedMojo extends AbstractMojo
* @parameter alias="useTestClasspath" default-value="false"
*/
private boolean useTestScope;
-
-
+
+
/**
* The default location of the web.xml file. Will be used
* if <webAppConfig><descriptor> is not set.
- *
+ *
* @parameter expression="${basedir}/src/main/webapp/WEB-INF/web.xml"
* @readonly
*/
private String webXml;
-
+
/**
* The target directory
- *
+ *
* @parameter expression="${project.build.directory}"
* @required
* @readonly
*/
protected File target;
-
-
+
+
/**
* The temporary directory to use for the webapp.
* Defaults to target/tmp
@@ -132,26 +145,26 @@ public class JettyRunForkedMojo extends AbstractMojo
*/
protected File tmpDirectory;
-
+
/**
* The directory containing generated classes.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
- *
+ *
*/
private File classesDirectory;
-
-
-
+
+
+
/**
* The directory containing generated test classes.
- *
+ *
* @parameter expression="${project.build.testOutputDirectory}"
* @required
*/
private File testClassesDirectory;
-
+
/**
* Root directory for all html/jsp etc files
*
@@ -159,34 +172,34 @@ public class JettyRunForkedMojo extends AbstractMojo
*
*/
private File webAppSourceDirectory;
-
-
+
+
/**
* Directories that contain static resources
* for the webapp. Optional.
- *
+ *
* @parameter
*/
private File[] resourceBases;
-
-
+
+
/**
- * If true, the webAppSourceDirectory will be first on the list of
- * resources that form the resource base for the webapp. If false,
+ * If true, the webAppSourceDirectory will be first on the list of
+ * resources that form the resource base for the webapp. If false,
* it will be last.
- *
+ *
* @parameter default-value="true"
*/
private boolean baseAppFirst;
-
+
/**
- * Location of jetty xml configuration files whose contents
+ * Location of jetty xml configuration files whose contents
* will be applied before any plugin configuration. Optional.
* @parameter
*/
private String jettyXml;
-
+
/**
* The context path for the webapp. Defaults to the
* name of the webapp's artifact.
@@ -205,71 +218,71 @@ public class JettyRunForkedMojo extends AbstractMojo
*/
private String contextXml;
-
- /**
+
+ /**
* @parameter expression="${jetty.skip}" default-value="false"
*/
private boolean skip;
/**
- * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort>
+ * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort>
* -DSTOP.KEY=<stopKey> -jar start.jar --stop
* @parameter
* @required
*/
protected int stopPort;
-
+
/**
- * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey>
+ * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey>
* -DSTOP.PORT=<stopPort> -jar start.jar --stop
* @parameter
* @required
*/
protected String stopKey;
-
+
/**
* Arbitrary jvm args to pass to the forked process
* @parameter
*/
private String jvmArgs;
-
-
+
+
/**
* @parameter expression="${plugin.artifacts}"
* @readonly
*/
private List pluginArtifacts;
-
-
+
+
/**
* @parameter expression="${plugin}"
* @readonly
*/
private PluginDescriptor plugin;
-
-
-
+
+
+
/**
* @parameter expression="true" default-value="true"
*/
private boolean waitForChild;
-
+
private Process forkedProcess;
-
+
private Random random;
-
-
-
+
+
+
public class ShutdownThread extends Thread
{
public ShutdownThread()
{
super("RunForkedShutdown");
}
-
+
public void run ()
{
if (forkedProcess != null && waitForChild)
@@ -278,7 +291,7 @@ public class JettyRunForkedMojo extends AbstractMojo
}
}
}
-
+
/**
* @see org.apache.maven.plugin.Mojo#execute()
*/
@@ -295,20 +308,20 @@ public class JettyRunForkedMojo extends AbstractMojo
random = new Random();
startJettyRunner();
}
-
-
+
+
public List getProvidedJars() throws MojoExecutionException
- {
+ {
//if we are configured to include the provided dependencies on the plugin's classpath
//(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
//try and filter out ones that will clash with jars that are plugin dependencies, then
//create a new classloader that we setup in the parent chain.
if (useProvidedScope)
{
-
- List provided = new ArrayList();
+
+ List provided = new ArrayList();
for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
- {
+ {
Artifact artifact = iter.next();
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
{
@@ -322,16 +335,16 @@ public class JettyRunForkedMojo extends AbstractMojo
else
return null;
}
-
+
/* ------------------------------------------------------------ */
public File prepareConfiguration() throws MojoExecutionException
{
try
- {
+ {
//work out the configuration based on what is configured in the pom
File propsFile = new File (target, "fork.props");
if (propsFile.exists())
- propsFile.delete();
+ propsFile.delete();
propsFile.createNewFile();
//propsFile.deleteOnExit();
@@ -358,9 +371,9 @@ public class JettyRunForkedMojo extends AbstractMojo
//sort out base dir of webapp
if (webAppSourceDirectory != null)
props.put("base.dir", webAppSourceDirectory.getAbsolutePath());
-
+
//sort out the resource base directories of the webapp
- StringBuilder builder = new StringBuilder();
+ StringBuilder builder = new StringBuilder();
if (baseAppFirst)
{
add((webAppSourceDirectory==null?null:webAppSourceDirectory.getAbsolutePath()), builder);
@@ -371,7 +384,7 @@ public class JettyRunForkedMojo extends AbstractMojo
}
}
else
- {
+ {
if (resourceBases != null)
{
for (File resDir:resourceBases)
@@ -380,7 +393,7 @@ public class JettyRunForkedMojo extends AbstractMojo
add((webAppSourceDirectory==null?null:webAppSourceDirectory.getAbsolutePath()), builder);
}
props.put("res.dirs", builder.toString());
-
+
//web-inf classes
List classDirs = getClassesDirs();
@@ -397,7 +410,7 @@ public class JettyRunForkedMojo extends AbstractMojo
{
props.put("classes.dir", classesDirectory.getAbsolutePath());
}
-
+
if (useTestScope && testClassesDirectory != null)
{
props.put("testClasses.dir", testClassesDirectory.getAbsolutePath());
@@ -435,7 +448,7 @@ public class JettyRunForkedMojo extends AbstractMojo
throw new MojoExecutionException("Prepare webapp configuration", e);
}
}
-
+
private void add (String string, StringBuilder builder)
{
if (string == null)
@@ -448,62 +461,62 @@ public class JettyRunForkedMojo extends AbstractMojo
private List getClassesDirs ()
{
List classesDirs = new ArrayList();
-
+
//if using the test classes, make sure they are first
//on the list
if (useTestScope && (testClassesDirectory != null))
classesDirs.add(testClassesDirectory);
-
+
if (classesDirectory != null)
classesDirs.add(classesDirectory);
-
+
return classesDirs;
}
-
-
-
+
+
+
private List getOverlays()
throws MalformedURLException, IOException
{
List overlays = new ArrayList();
for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
{
- Artifact artifact = (Artifact) iter.next();
-
+ Artifact artifact = (Artifact) iter.next();
+
if (artifact.getType().equals("war"))
overlays.add(artifact.getFile());
}
return overlays;
}
-
-
-
+
+
+
private List getDependencyFiles ()
{
List dependencyFiles = new ArrayList();
-
+
for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
-
- if (((!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) && (!Artifact.SCOPE_TEST.equals( artifact.getScope())))
+
+ if (((!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) && (!Artifact.SCOPE_TEST.equals( artifact.getScope())))
||
(useTestScope && Artifact.SCOPE_TEST.equals( artifact.getScope())))
{
dependencyFiles.add(artifact.getFile());
- getLog().debug( "Adding artifact " + artifact.getFile().getName() + " for WEB-INF/lib " );
+ getLog().debug( "Adding artifact " + artifact.getFile().getName() + " for WEB-INF/lib " );
}
}
-
- return dependencyFiles;
+
+ return dependencyFiles;
}
-
+
public boolean isPluginArtifact(Artifact artifact)
{
if (pluginArtifacts == null || pluginArtifacts.isEmpty())
return false;
-
+
boolean isPluginArtifact = false;
for (Iterator iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
{
@@ -512,18 +525,18 @@ public class JettyRunForkedMojo extends AbstractMojo
if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
isPluginArtifact = true;
}
-
+
return isPluginArtifact;
}
-
-
-
+
+
+
private Set getExtraJars()
throws Exception
{
Set extraJars = new HashSet();
-
-
+
+
List l = pluginArtifacts;
Artifact pluginArtifact = null;
@@ -531,7 +544,7 @@ public class JettyRunForkedMojo extends AbstractMojo
{
Iterator itor = l.iterator();
while (itor.hasNext() && pluginArtifact == null)
- {
+ {
Artifact a = (Artifact)itor.next();
if (a.getArtifactId().equals(plugin.getArtifactId())) //get the jetty-maven-plugin jar
{
@@ -543,18 +556,18 @@ public class JettyRunForkedMojo extends AbstractMojo
return extraJars;
}
-
+
/* ------------------------------------------------------------ */
public void startJettyRunner() throws MojoExecutionException
- {
+ {
try
{
-
+
File props = prepareConfiguration();
-
+
List cmd = new ArrayList();
cmd.add(getJavaBin());
-
+
if (jvmArgs != null)
{
String[] args = jvmArgs.split(" ");
@@ -564,7 +577,7 @@ public class JettyRunForkedMojo extends AbstractMojo
cmd.add(args[i].trim());
}
}
-
+
String classPath = getClassPath();
if (classPath != null && classPath.length() > 0)
{
@@ -572,7 +585,7 @@ public class JettyRunForkedMojo extends AbstractMojo
cmd.add(classPath);
}
cmd.add(Starter.class.getCanonicalName());
-
+
if (stopPort > 0 && stopKey != null)
{
cmd.add("--stop-port");
@@ -585,26 +598,26 @@ public class JettyRunForkedMojo extends AbstractMojo
cmd.add("--jetty-xml");
cmd.add(jettyXml);
}
-
+
if (contextXml != null)
{
cmd.add("--context-xml");
cmd.add(contextXml);
}
-
+
cmd.add("--props");
cmd.add(props.getAbsolutePath());
-
+
String token = createToken();
cmd.add("--token");
cmd.add(token);
-
+
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.directory(project.getBasedir());
-
+
if (PluginLog.getLog().isDebugEnabled())
PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
-
+
forkedProcess = builder.start();
PluginLog.getLog().info("Forked process starting");
@@ -612,7 +625,7 @@ public class JettyRunForkedMojo extends AbstractMojo
{
startPump("STDOUT",forkedProcess.getInputStream());
startPump("STDERR",forkedProcess.getErrorStream());
- int exitcode = forkedProcess.waitFor();
+ int exitcode = forkedProcess.waitFor();
PluginLog.getLog().info("Forked execution exit: "+exitcode);
}
else
@@ -652,20 +665,20 @@ public class JettyRunForkedMojo extends AbstractMojo
{
if (forkedProcess != null && waitForChild)
forkedProcess.destroy();
-
+
throw new MojoExecutionException("Failed to start Jetty within time limit");
}
catch (Exception ex)
{
if (forkedProcess != null && waitForChild)
forkedProcess.destroy();
-
+
throw new MojoExecutionException("Failed to create Jetty process", ex);
}
}
-
-
-
+
+
+
public String getClassPath() throws Exception
{
StringBuilder classPath = new StringBuilder();
@@ -682,16 +695,16 @@ public class JettyRunForkedMojo extends AbstractMojo
}
}
-
+
//Any jars that we need from the plugin environment (like the ones containing Starter class)
Set extraJars = getExtraJars();
for (Artifact a:extraJars)
- {
+ {
classPath.append(File.pathSeparator);
classPath.append(a.getFile().getAbsolutePath());
}
-
-
+
+
//Any jars that we need from the project's dependencies because we're useProvided
List providedJars = getProvidedJars();
if (providedJars != null && !providedJars.isEmpty())
@@ -703,7 +716,7 @@ public class JettyRunForkedMojo extends AbstractMojo
if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
}
}
-
+
return classPath.toString();
}
@@ -724,7 +737,7 @@ public class JettyRunForkedMojo extends AbstractMojo
return "java";
}
-
+
public static String fileSeparators(String path)
{
StringBuilder ret = new StringBuilder();
@@ -759,13 +772,13 @@ public class JettyRunForkedMojo extends AbstractMojo
return ret.toString();
}
-
+
private String createToken ()
{
- return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase();
+ return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH);
}
-
-
+
+
private void startPump(String mode, InputStream inputStream)
{
ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
@@ -774,7 +787,7 @@ public class JettyRunForkedMojo extends AbstractMojo
thread.start();
}
-
+
/**
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
index acd37629849..f8addd006c6 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
@@ -63,6 +63,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
public class JettyRunMojo extends AbstractJettyMojo
{
+ public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp";
+
+
/**
* If true, the <testOutputDirectory>
* and the dependencies of <scope>test<scope>
@@ -134,12 +137,13 @@ public class JettyRunMojo extends AbstractJettyMojo
*/
private List extraScanTargets;
-
+
+
/**
* Verify the configuration given in the pom.
*
- * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ * @see org.mortbay.jetty.plugin.AbstractJettyMojo#checkPomConfiguration()
*/
public void checkPomConfiguration () throws MojoExecutionException
{
@@ -147,9 +151,10 @@ public class JettyRunMojo extends AbstractJettyMojo
try
{
if ((getWebAppSourceDirectory() == null) || !getWebAppSourceDirectory().exists())
- {
- webAppSourceDirectory = new File (project.getBasedir(), "src"+File.separator+"main"+File.separator+"webapp");
- getLog().info("webAppSourceDirectory "+getWebAppSourceDirectory() +" does not exist. Defaulting to "+webAppSourceDirectory.getAbsolutePath());
+ {
+ File defaultWebAppSrcDir = new File (project.getBasedir(), DEFAULT_WEBAPP_SRC);
+ getLog().info("webAppSourceDirectory"+(getWebAppSourceDirectory()==null?" not set.":" does not exist.")+" Defaulting to "+defaultWebAppSrcDir.getAbsolutePath());
+ webAppSourceDirectory = defaultWebAppSrcDir;
}
else
getLog().info( "Webapp source directory = " + getWebAppSourceDirectory().getCanonicalPath());
@@ -441,6 +446,7 @@ public class JettyRunMojo extends AbstractJettyMojo
for ( Iterator iter = projectArtifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
+
// Include runtime and compile time libraries, and possibly test libs too
if(artifact.getType().equals("war"))
{
@@ -448,6 +454,7 @@ public class JettyRunMojo extends AbstractJettyMojo
{
Resource r=Resource.newResource("jar:"+Resource.toURL(artifact.getFile()).toString()+"!/");
overlays.add(r);
+ getLog().info("Adding overlay for war project artifact "+artifact.getId());
getExtraScanTargets().add(artifact.getFile());
}
catch(Exception e)
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
index c1e6a855d21..33e344dfa03 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
@@ -25,19 +25,21 @@ import java.util.List;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
/**
- *
+ *
*
* This goal is used to assemble your webapp into an exploded war and automatically deploy it to Jetty.
*
*
- * Once invoked, the plugin can be configured to run continuously, scanning for changes in the pom.xml and
- * to WEB-INF/web.xml, WEB-INF/classes or WEB-INF/lib and hot redeploy when a change is detected.
+ * Once invoked, the plugin can be configured to run continuously, scanning for changes in the pom.xml and
+ * to WEB-INF/web.xml, WEB-INF/classes or WEB-INF/lib and hot redeploy when a change is detected.
*
*
* You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
- * This can be used, for example, to deploy a static webapp that is not part of your maven build.
+ * This can be used, for example, to deploy a static webapp that is not part of your maven build.
*
*
* There is a reference guide to the configuration parameters for this plugin, and more detailed information
@@ -51,24 +53,24 @@ import org.eclipse.jetty.util.Scanner;
public class JettyRunWarExplodedMojo extends AbstractJettyMojo
{
-
-
+
+
/**
* The location of the war file.
- *
+ *
* @parameter alias="webApp" expression="${project.build.directory}/${project.build.finalName}"
* @required
*/
private File war;
-
-
-
-
+
+
+
+
/**
- *
- * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ *
+ * @see org.mortbay.jetty.plugin.AbstractJettyMojo#checkPomConfiguration()
*/
public void checkPomConfiguration() throws MojoExecutionException
{
@@ -76,7 +78,7 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
}
/**
- * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureScanner()
+ * @see org.mortbay.jetty.plugin.AbstractJettyMojo#configureScanner()
*/
public void configureScanner() throws MojoExecutionException
{
@@ -93,7 +95,7 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
scanList.add(new File(webInfDir, "classes"));
scanList.add(new File(webInfDir, "lib"));
setScanList(scanList);
-
+
ArrayList listeners = new ArrayList();
listeners.add(new Scanner.BulkListener()
{
@@ -113,10 +115,10 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
setScannerListeners(listeners);
}
-
-
-
- public void restartWebApp(boolean reconfigureScanner) throws Exception
+
+
+
+ public void restartWebApp(boolean reconfigureScanner) throws Exception
{
getLog().info("Restarting webapp");
getLog().debug("Stopping webapp ...");
@@ -152,20 +154,20 @@ public class JettyRunWarExplodedMojo extends AbstractJettyMojo
getLog().info("Restart completed.");
}
-
-
+
+
public void configureWebApplication () throws Exception
{
- super.configureWebApplication();
+ super.configureWebApplication();
webApp.setWar(war.getCanonicalPath());
}
-
+
public void execute () throws MojoExecutionException, MojoFailureException
{
super.execute();
}
-
-
-
+
+
+
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
index 9c320c968c7..35fc923aebe 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
@@ -21,10 +21,14 @@ package org.eclipse.jetty.maven.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
/**
*
* Once invoked, the plugin can be configured to run continuously, scanning for changes in the project and to the
- * war file and automatically performing a
- * hot redeploy when necessary.
+ * war file and automatically performing a
+ * hot redeploy when necessary.
*
*
* You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
- * This can be used, for example, to deploy a static webapp that is not part of your maven build.
+ * This can be used, for example, to deploy a static webapp that is not part of your maven build.
*
*
* There is a reference guide to the configuration parameters for this plugin, and more detailed information
* with examples in the Configuration Guide.
*
- *
+ *
* @goal run-war
* @requiresDependencyResolution compile+runtime
* @execute phase="package"
@@ -61,13 +65,13 @@ public class JettyRunWarMojo extends AbstractJettyMojo
private File war;
-
+
/**
* @see org.apache.maven.plugin.Mojo#execute()
*/
public void execute() throws MojoExecutionException, MojoFailureException
{
- super.execute();
+ super.execute();
}
@@ -75,18 +79,18 @@ public class JettyRunWarMojo extends AbstractJettyMojo
public void configureWebApplication () throws Exception
{
super.configureWebApplication();
-
+
webApp.setWar(war.getCanonicalPath());
}
-
+
/**
- * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ * @see org.mortbay.jetty.plugin.AbstractJettyMojo#checkPomConfiguration()
*/
public void checkPomConfiguration() throws MojoExecutionException
{
- return;
+ return;
}
@@ -100,7 +104,7 @@ public class JettyRunWarMojo extends AbstractJettyMojo
scanList.add(getProject().getFile());
scanList.add(war);
setScanList(scanList);
-
+
ArrayList listeners = new ArrayList();
listeners.add(new Scanner.BulkListener()
{
@@ -117,11 +121,11 @@ public class JettyRunWarMojo extends AbstractJettyMojo
}
}
});
- setScannerListeners(listeners);
+ setScannerListeners(listeners);
}
- public void restartWebApp(boolean reconfigureScanner) throws Exception
+ public void restartWebApp(boolean reconfigureScanner) throws Exception
{
getLog().info("Restarting webapp ...");
getLog().debug("Stopping webapp ...");
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
index 56cbecd035e..b0a38bf4988 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
@@ -23,7 +23,6 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
@@ -33,20 +32,20 @@ import org.eclipse.jetty.webapp.WebAppContext;
/**
* JettyServer
- *
+ *
* Maven jetty plugin version of a wrapper for the Server class.
- *
+ *
*/
public class JettyServer extends org.eclipse.jetty.server.Server
{
public static int DEFAULT_PORT = 8080;
public static int DEFAULT_MAX_IDLE_TIME = 30000;
-
+
private RequestLog requestLog;
private ContextHandlerCollection contexts;
-
-
+
+
public JettyServer()
{
super();
@@ -55,7 +54,7 @@ public class JettyServer extends org.eclipse.jetty.server.Server
Resource.setDefaultUseCaches(false);
}
-
+
public void setRequestLog (RequestLog requestLog)
{
this.requestLog = requestLog;
@@ -69,16 +68,16 @@ public class JettyServer extends org.eclipse.jetty.server.Server
super.doStart();
}
-
+
/**
* @see org.eclipse.jetty.server.handler.HandlerCollection#addHandler(org.eclipse.jetty.server.Handler)
*/
public void addWebApplication(WebAppContext webapp) throws Exception
- {
+ {
contexts.addHandler (webapp);
}
-
+
/**
* Set up the handler structure to receive a webapp.
* Also put in a DefaultHandler so we get a nice page
@@ -86,43 +85,43 @@ public class JettyServer extends org.eclipse.jetty.server.Server
* context isn't at root.
* @throws Exception
*/
- public void configureHandlers () throws Exception
+ public void configureHandlers () throws Exception
{
DefaultHandler defaultHandler = new DefaultHandler();
RequestLogHandler requestLogHandler = new RequestLogHandler();
if (this.requestLog != null)
requestLogHandler.setRequestLog(this.requestLog);
-
+
contexts = (ContextHandlerCollection)super.getChildHandlerByClass(ContextHandlerCollection.class);
if (contexts==null)
- {
+ {
contexts = new ContextHandlerCollection();
HandlerCollection handlers = (HandlerCollection)super.getChildHandlerByClass(HandlerCollection.class);
if (handlers==null)
{
- handlers = new HandlerCollection();
- super.setHandler(handlers);
+ handlers = new HandlerCollection();
+ super.setHandler(handlers);
handlers.setHandlers(new Handler[]{contexts, defaultHandler, requestLogHandler});
}
else
{
handlers.addHandler(contexts);
}
- }
+ }
}
-
-
-
-
- public Connector createDefaultConnector(Server server, String portnum) throws Exception
+
+
+
+
+ public Connector createDefaultConnector(String portnum) throws Exception
{
- ServerConnector connector = new ServerConnector(server);
+ ServerConnector connector = new ServerConnector(this);
int port = ((portnum==null||portnum.equals(""))?DEFAULT_PORT:Integer.parseInt(portnum.trim()));
connector.setPort(port);
- connector.setIdleTimeout(DEFAULT_MAX_IDLE_TIME);
-
+ // connector.setMaxIdleTime(DEFAULT_MAX_IDLE_TIME);
+
return connector;
}
-
-
+
+
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
index f6b4e122a5a..4a2dcd7308a 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
@@ -28,7 +28,6 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
@@ -39,8 +38,8 @@ import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
-import org.eclipse.jetty.webapp.TagLibConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
/**
@@ -56,6 +55,7 @@ public class JettyWebAppContext extends WebAppContext
{
private static final Logger LOG = Log.getLogger(JettyWebAppContext.class);
+ private static final String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN = ".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$";
private static final String WEB_INF_CLASSES_PREFIX = "/WEB-INF/classes";
private static final String WEB_INF_LIB_PREFIX = "/WEB-INF/lib";
@@ -73,6 +73,19 @@ public class JettyWebAppContext extends WebAppContext
* @deprecated The value of this parameter will be ignored by the plugin. Overlays will always be unpacked.
*/
private boolean unpackOverlays;
+
+ /**
+ * Set the "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern" with a pattern for matching jars on
+ * container classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
+ */
+ private String containerIncludeJarPattern = null;
+
+ /**
+ * Set the "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern" with a pattern for matching jars on
+ * webapp's classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
+ */
+ private String webInfIncludeJarPattern = null;
+
/**
* @deprecated The value of this parameter will be ignored by the plugin. This option will be always disabled.
@@ -91,14 +104,34 @@ public class JettyWebAppContext extends WebAppContext
new MetaInfConfiguration(),
new FragmentConfiguration(),
envConfig = new EnvConfiguration(),
- new AnnotationConfiguration(),
new org.eclipse.jetty.plus.webapp.PlusConfiguration(),
- new JettyWebXmlConfiguration(),
- new TagLibConfiguration()
+ new MavenAnnotationConfiguration(),
+ new JettyWebXmlConfiguration()
});
// Turn off copyWebInf option as it is not applicable for plugin.
super.setCopyWebInf(false);
}
+ public void setContainerIncludeJarPattern(String pattern)
+ {
+ containerIncludeJarPattern = pattern;
+ }
+
+ public String getContainerIncludeJarPattern()
+ {
+ return containerIncludeJarPattern;
+ }
+
+
+ public String getWebInfIncludeJarPattern()
+ {
+ return webInfIncludeJarPattern;
+ }
+ public void setWebInfIncludeJarPattern(String pattern)
+ {
+ webInfIncludeJarPattern = pattern;
+ }
+
+
public boolean getUnpackOverlays()
{
@@ -218,17 +251,21 @@ public class JettyWebAppContext extends WebAppContext
{
//Set up the pattern that tells us where the jars are that need scanning for
//stuff like taglibs so we can tell jasper about it (see TagLibConfiguration)
- String tmp = (String)getAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern");
-
- tmp = addPattern(tmp, ".*/.*jsp-api-[^/]*\\.jar$");
- tmp = addPattern(tmp, ".*/.*jsp-[^/]*\\.jar$");
- tmp = addPattern(tmp, ".*/.*taglibs[^/]*\\.jar$");
- tmp = addPattern(tmp, ".*/.*jstl[^/]*\\.jar$");
- tmp = addPattern(tmp, ".*/.*jsf-impl-[^/]*\\.jar$"); // add in 2 most popular jsf impls
- tmp = addPattern(tmp, ".*/.*javax.faces-[^/]*\\.jar$");
- tmp = addPattern(tmp, ".*/.*myfaces-impl-[^/]*\\.jar$");
- setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", tmp);
+ //Allow user to set up pattern for names of jars from the container classpath
+ //that will be scanned - note that by default NO jars are scanned
+ String tmp = containerIncludeJarPattern;
+ if (tmp==null || "".equals(tmp))
+ tmp = (String)getAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN);
+
+ tmp = addPattern(tmp, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN);
+ setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, tmp);
+
+ //Allow user to set up pattern of jar names from WEB-INF that will be scanned.
+ //Note that by default ALL jars considered to be in WEB-INF will be scanned - setting
+ //a pattern restricts scanning
+ if (webInfIncludeJarPattern != null)
+ setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, webInfIncludeJarPattern);
//Set up the classes dirs that comprises the equivalent of WEB-INF/classes
if (testClasses != null)
@@ -241,7 +278,6 @@ public class JettyWebAppContext extends WebAppContext
classpathFiles.addAll(webInfClasses);
classpathFiles.addAll(webInfJars);
-
// Initialize map containing all jars in /WEB-INF/lib
webInfJarMap.clear();
for (File file : webInfJars)
@@ -255,13 +291,28 @@ public class JettyWebAppContext extends WebAppContext
if (this.jettyEnvXml != null)
envConfig.setJettyEnvXml(Resource.toURL(new File(this.jettyEnvXml)));
- //setShutdown(false);
+ // CHECK setShutdown(false);
super.doStart();
}
public void doStop () throws Exception
{
- //setShutdown(true);
+ if (classpathFiles != null)
+ classpathFiles.clear();
+ classpathFiles = null;
+
+ classes = null;
+ testClasses = null;
+
+ if (webInfJarMap != null)
+ webInfJarMap.clear();
+
+ webInfClasses.clear();
+ webInfJars.clear();
+
+
+
+ // CHECK setShutdown(true);
//just wait a little while to ensure no requests are still being processed
Thread.currentThread().sleep(500L);
super.doStop();
@@ -344,37 +395,40 @@ public class JettyWebAppContext extends WebAppContext
@Override
public Set getResourcePaths(String path)
{
- // Try to get regular resource paths
+ // Try to get regular resource paths - this will get appropriate paths from any overlaid wars etc
Set paths = super.getResourcePaths(path);
-
- // If no paths are returned check for virtual paths /WEB-INF/classes and /WEB-INF/lib
- if (paths.isEmpty() && path != null)
+
+ if (path != null)
{
- path = URIUtil.canonicalPath(path);
+ TreeSet allPaths = new TreeSet();
+ allPaths.addAll(paths);
+
+ //add in the dependency jars as a virtual WEB-INF/lib entry
if (path.startsWith(WEB_INF_LIB_PREFIX))
{
- paths = new TreeSet();
for (String fileName : webInfJarMap.keySet())
{
// Return all jar files from class path
- paths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
+ allPaths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
}
}
else if (path.startsWith(WEB_INF_CLASSES_PREFIX))
{
int i=0;
- while (paths.isEmpty() && (i < webInfClasses.size()))
+ while (i < webInfClasses.size())
{
String newPath = path.replace(WEB_INF_CLASSES_PREFIX, webInfClasses.get(i).getPath());
- paths = super.getResourcePaths(newPath);
+ allPaths.addAll(super.getResourcePaths(newPath));
i++;
}
}
+ return allPaths;
}
return paths;
}
+
public String addPattern (String s, String pattern)
{
if (s == null)
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenAnnotationConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenAnnotationConfiguration.java
new file mode 100644
index 00000000000..566b4af53f4
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenAnnotationConfiguration.java
@@ -0,0 +1,109 @@
+//
+// ========================================================================
+// 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.maven.plugin;
+
+import java.io.File;
+
+import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.annotations.AnnotationParser;
+import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.ClassNameResolver;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.MetaData;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class MavenAnnotationConfiguration extends AnnotationConfiguration
+{
+ private static final Logger LOG = Log.getLogger(MavenAnnotationConfiguration.class);
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void parseWebInfClasses(final WebAppContext context, final AnnotationParser parser) throws Exception
+ {
+ JettyWebAppContext jwac = (JettyWebAppContext)context;
+ if (jwac.getClassPathFiles() == null || jwac.getClassPathFiles().size() == 0)
+ super.parseWebInfClasses (context, parser);
+ else
+ {
+ LOG.debug("Scanning classes ");
+ //Look for directories on the classpath and process each one of those
+
+ MetaData metaData = context.getMetaData();
+ if (metaData == null)
+ throw new IllegalStateException ("No metadata");
+
+ parser.clearHandlers();
+ for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
+ {
+ if (h instanceof AbstractDiscoverableAnnotationHandler)
+ ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
+ }
+ parser.registerHandlers(_discoverableAnnotationHandlers);
+ parser.registerHandler(_classInheritanceHandler);
+ parser.registerHandlers(_containerInitializerAnnotationHandlers);
+
+
+ for (File f:jwac.getClassPathFiles())
+ {
+ //scan the equivalent of the WEB-INF/classes directory that has been synthesised by the plugin
+ if (f.isDirectory() && f.exists())
+ {
+ doParse(context, parser, Resource.newResource(f.toURL()));
+ }
+ }
+
+ //if an actual WEB-INF/classes directory also exists (eg because of overlayed wars) then scan that
+ //too
+ if (context.getWebInf() != null && context.getWebInf().exists())
+ {
+ Resource classesDir = context.getWebInf().addPath("classes/");
+ if (classesDir.exists())
+ {
+ doParse(context, parser, classesDir);
+ }
+ }
+ }
+ }
+
+
+ public void doParse (final WebAppContext context, final AnnotationParser parser, Resource resource)
+ throws Exception
+ {
+ parser.parse(resource, new ClassNameResolver()
+ {
+ public boolean isExcluded (String name)
+ {
+ if (context.isSystemClass(name)) return true;
+ if (context.isServerClass(name)) return false;
+ return false;
+ }
+
+ public boolean shouldOverride (String name)
+ {
+ //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
+ if (context.isParentLoaderPriority())
+ return false;
+ return true;
+ }
+ });
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
index 6048cae1d19..5d439e3e71d 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
@@ -24,8 +24,10 @@ import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@@ -40,8 +42,8 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
protected Resource _originalResourceBase;
protected Resource[] _unpackedOverlays;
-
-
+
+
public void configure(WebAppContext context) throws Exception
{
JettyWebAppContext jwac = (JettyWebAppContext)context;
@@ -54,11 +56,11 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
while (itor.hasNext())
((WebAppClassLoader)context.getClassLoader()).addClassPath(((File)itor.next()).getCanonicalPath());
- if (LOG.isDebugEnabled())
- LOG.debug("Classpath = "+((URLClassLoader)context.getClassLoader()).getURLs());
+ //if (LOG.isDebugEnabled())
+ //LOG.debug("Classpath = "+LazyList.array2List(((URLClassLoader)context.getClassLoader()).getURLs()));
}
super.configure(context);
-
+
// knock out environmental maven and plexus classes from webAppContext
String[] existingServerClasses = context.getServerClasses();
String[] newServerClasses = new String[2+(existingServerClasses==null?0:existingServerClasses.length)];
@@ -71,7 +73,7 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
for (int i=0;i0)
+ {
+ try
+ {
+ for (int i=0; i<_unpackedOverlays.length; i++)
+ {
+ IO.delete(_unpackedOverlays[i].getFile());
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ super.deconfigure(context);
+ //restore whatever the base resource was before we might have included overlaid wars
+ context.setBaseResource(_originalResourceBase);
+
+ }
+
+
+
+
+ /**
+ * @see org.eclipse.jetty.webapp.WebInfConfiguration#unpack(org.eclipse.jetty.webapp.WebAppContext)
+ */
+ @Override
+ public void unpack(WebAppContext context) throws IOException
+ {
+ //Unpack and find base resource as normal
+ super.unpack(context);
+
+
+ //Add in any overlays as a resource collection for the base
_originalResourceBase = context.getBaseResource();
JettyWebAppContext jwac = (JettyWebAppContext)context;
@@ -102,7 +151,7 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
origSize = 1;
}
}
-
+
int overlaySize = jwac.getOverlays().size();
Resource[] newResources = new Resource[origSize + overlaySize];
@@ -112,7 +161,6 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
if (jwac.getBaseAppFirst())
{
System.arraycopy(origResources,0,newResources,0,origSize);
-
offset = origSize;
}
else
@@ -120,53 +168,23 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
System.arraycopy(origResources,0,newResources,overlaySize,origSize);
}
}
-
+
// Overlays are always unpacked
_unpackedOverlays = new Resource[overlaySize];
List overlays = jwac.getOverlays();
for (int idx=0; idx0)
- {
- try
- {
- for (int i=0; i<_unpackedOverlays.length; i++)
- {
- IO.delete(_unpackedOverlays[i].getFile());
- }
- }
- catch (IOException e)
- {
- LOG.ignore(e);
- }
- }
- super.deconfigure(context);
- //restore whatever the base resource was before we might have included overlaid wars
- context.setBaseResource(_originalResourceBase);
-
- }
-
/**
* Get the jars to examine from the files from which we have
@@ -175,6 +193,7 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
* @param context
* @return the list of jars found
*/
+ @Override
protected List findJars (WebAppContext context)
throws Exception
{
@@ -184,7 +203,7 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
{
for (File f: jwac.getClassPathFiles())
{
- if (f.getName().toLowerCase().endsWith(".jar"))
+ if (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar"))
{
try
{
@@ -203,14 +222,16 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
list.addAll(superList);
return list;
}
+
+
protected Resource unpackOverlay (WebAppContext context, Resource overlay)
throws IOException
{
//resolve if not already resolved
resolveTempDirectory(context);
-
-
+
+
//Get the name of the overlayed war and unpack it to a dir of the
//same name in the temporary directory
String name = overlay.getName();
@@ -225,6 +246,4 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
Resource unpackedOverlay = Resource.newResource(dir.getCanonicalPath());
return unpackedOverlay;
}
-
-
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
index 66e6d1529c1..2ef290ea08a 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
@@ -16,12 +16,12 @@
// ========================================================================
//
-
package org.eclipse.jetty.maven.plugin;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
@@ -41,7 +41,7 @@ import org.eclipse.jetty.server.Server;
* by stopping the Server instances. The choice of
* behaviour is controlled by either passing true
* (exit jvm) or false (stop Servers) in the constructor.
- *
+ *
*/
public class Monitor extends Thread
{
@@ -51,7 +51,7 @@ public class Monitor extends Thread
ServerSocket _serverSocket;
boolean _kill;
- public Monitor(int port, String key, Server[] servers, boolean kill)
+ public Monitor(int port, String key, Server[] servers, boolean kill)
throws UnknownHostException, IOException
{
if (port <= 0)
@@ -64,7 +64,7 @@ public class Monitor extends Thread
_kill = kill;
setDaemon(true);
setName("StopJettyPluginMonitor");
- InetSocketAddress address = new InetSocketAddress("127.0.0.1", port);
+ InetSocketAddress address = new InetSocketAddress("127.0.0.1", port);
_serverSocket=new ServerSocket();
_serverSocket.setReuseAddress(true);
try
@@ -77,7 +77,7 @@ public class Monitor extends Thread
throw x;
}
}
-
+
public void run()
{
while (_serverSocket != null)
@@ -88,7 +88,7 @@ public class Monitor extends Thread
socket = _serverSocket.accept();
socket.setSoLinger(false, 0);
LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
-
+
String key = lin.readLine();
if (!_key.equals(key)) continue;
String cmd = lin.readLine();
@@ -97,13 +97,13 @@ public class Monitor extends Thread
try{socket.close();}catch (Exception e){e.printStackTrace();}
try{socket.close();}catch (Exception e){e.printStackTrace();}
try{_serverSocket.close();}catch (Exception e){e.printStackTrace();}
-
+
_serverSocket = null;
-
+
if (_kill)
{
System.out.println("Killing Jetty");
- System.exit(0);
+ System.exit(0);
}
else
{
@@ -111,7 +111,7 @@ public class Monitor extends Thread
{
try
{
- System.out.println("Stopping server "+i);
+ System.out.println("Stopping server "+i);
_servers[i].stop();
}
catch (Exception e)
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
index ef2c1092ecf..93eb1e31721 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
@@ -23,19 +23,23 @@ import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
+import java.util.StringTokenizer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
public class Starter
-{
+{
public static final String PORT_SYSPROPERTY = "jetty.port";
private static final Logger LOG = Log.getLogger(Starter.class);
@@ -45,21 +49,21 @@ public class Starter
private JettyServer server;
private JettyWebAppContext webApp;
private Monitor monitor;
-
+
private int stopPort=0;
private String stopKey=null;
private Properties props;
private String token;
-
-
+
+
public void configureJetty () throws Exception
{
LOG.debug("Starting Jetty Server ...");
this.server = new JettyServer();
- //apply any configs from jetty.xml files first
+ //apply any configs from jetty.xml files first
applyJettyXml ();
// if the user hasn't configured a connector in the jetty.xml
@@ -68,7 +72,7 @@ public class Starter
if (connectors == null|| connectors.length == 0)
{
//if a SystemProperty -Djetty.port= has been supplied, use that as the default port
- connectors = new Connector[] { this.server.createDefaultConnector(server, System.getProperty(PORT_SYSPROPERTY, null)) };
+ connectors = new Connector[] { this.server.createDefaultConnector(System.getProperty(PORT_SYSPROPERTY, null)) };
this.server.setConnectors(connectors);
}
@@ -84,15 +88,15 @@ public class Starter
this.server.configureHandlers();
webApp = new JettyWebAppContext();
-
+
//configure webapp from properties file describing unassembled webapp
configureWebApp();
-
+
//set up the webapp from the context xml file provided
//NOTE: just like jetty:run mojo this means that the context file can
//potentially override settings made in the pom. Ideally, we'd like
//the pom to override the context xml file, but as the other mojos all
- //configure a WebAppContext in the pom (the element), it is
+ //configure a WebAppContext in the pom (the element), it is
//already configured by the time the context xml file is applied.
if (contextXml != null)
{
@@ -109,30 +113,30 @@ public class Starter
monitor = new Monitor(stopPort, stopKey, new Server[]{server}, true);
}
}
-
-
+
+
public void configureWebApp ()
throws Exception
{
if (props == null)
return;
-
+
//apply a properties file that defines the things that we configure in the jetty:run plugin:
// - the context path
String str = (String)props.get("context.path");
if (str != null)
webApp.setContextPath(str);
-
+
// - web.xml
str = (String)props.get("web.xml");
if (str != null)
webApp.setDescriptor(str);
-
+
// - the tmp directory
str = (String)props.getProperty("tmp.dir");
if (str != null)
webApp.setTempDirectory(new File(str.trim()));
-
+
// - the base directory
str = (String)props.getProperty("base.dir");
if (str != null && !"".equals(str.trim()))
@@ -145,7 +149,7 @@ public class Starter
ResourceCollection resources = new ResourceCollection(str);
webApp.setBaseResource(resources);
}
-
+
// - overlays
str = (String)props.getProperty("overlay.files");
if (str != null && !"".equals(str.trim()))
@@ -163,8 +167,8 @@ public class Starter
{
webApp.setClasses(new File(str));
}
-
- str = (String)props.getProperty("testClasses.dir");
+
+ str = (String)props.getProperty("testClasses.dir");
if (str != null && !"".equals(str.trim()))
{
webApp.setTestClasses(new File(str));
@@ -181,7 +185,7 @@ public class Starter
jars.add(new File(names[j].trim()));
webApp.setWebInfLib(jars);
}
-
+
}
public void getConfiguration (String[] args)
@@ -205,7 +209,7 @@ public class Starter
for (int j=0; names!= null && j < names.length; j++)
{
jettyXmls.add(new File(names[j].trim()));
- }
+ }
}
//--context-xml
@@ -221,7 +225,7 @@ public class Starter
props = new Properties();
props.load(new FileInputStream(f));
}
-
+
//--token
if ("--token".equals(args[i]))
{
@@ -237,16 +241,16 @@ public class Starter
monitor.start();
LOG.info("Started Jetty Server");
- server.start();
+ server.start();
}
-
+
public void join () throws Exception
{
server.join();
}
-
-
+
+
public void communicateStartupResult (Exception e)
{
if (token != null)
@@ -257,16 +261,16 @@ public class Starter
System.out.println(token+"\t"+e.getMessage());
}
}
-
-
+
+
public void applyJettyXml() throws Exception
{
if (jettyXmls == null)
return;
-
+
for ( File xmlFile : jettyXmls )
{
- LOG.info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );
+ LOG.info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );
XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
xmlConfiguration.configure(this.server);
}
@@ -286,8 +290,8 @@ public class Starter
System.arraycopy(existing, 0, children, 1, existing.length);
handlers.setHandlers(children);
}
-
-
+
+
public static final void main(String[] args)
{
if (args == null)
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java
index aa7a6b43ce3..2cdf9b03bda 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java
@@ -23,6 +23,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
@@ -116,7 +117,7 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
*/
private static String getDeployedAppName(String contextFileName)
{
- String lowername = contextFileName.toLowerCase();
+ String lowername = contextFileName.toLowerCase(Locale.ENGLISH);
if (lowername.endsWith(".xml"))
{
String contextName = contextFileName.substring(0, lowername.length() - ".xml".length());
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
index 6a3010db277..6e1f7b9b5af 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -109,7 +110,7 @@ public class LibExtClassLoaderHelper
for (File f : jettyResources.listFiles())
{
jettyResFiles.put(f.getName(), f);
- if (f.getName().toLowerCase().startsWith("readme"))
+ if (f.getName().toLowerCase(Locale.ENGLISH).startsWith("readme"))
{
continue;
}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
index 5cfad9a248a..da94978a726 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.plus.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.util.Locale;
import javax.naming.InitialContext;
import javax.naming.NamingException;
@@ -140,7 +141,7 @@ public class Injection
_resourceClass = resourceType;
//first look for a javabeans style setter matching the targetName
- String setter = "set"+target.substring(0,1).toUpperCase()+target.substring(1);
+ String setter = "set"+target.substring(0,1).toUpperCase(Locale.ENGLISH)+target.substring(1);
try
{
LOG.debug("Looking for method for setter: "+setter+" with arg "+_resourceClass);
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
index ae9e906a172..ffad0f617f2 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
@@ -26,6 +26,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
@@ -413,7 +414,7 @@ public class DataSourceLoginService extends MappedLoginService
DatabaseMetaData metaData = connection.getMetaData();
//check if tables exist
- String tableName = (metaData.storesLowerCaseIdentifiers()? _userTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_userTableName.toUpperCase(): _userTableName));
+ String tableName = (metaData.storesLowerCaseIdentifiers()? _userTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userTableName.toUpperCase(Locale.ENGLISH): _userTableName));
ResultSet result = metaData.getTables(null, null, tableName, null);
if (!result.next())
{
@@ -431,7 +432,7 @@ public class DataSourceLoginService extends MappedLoginService
result.close();
- tableName = (metaData.storesLowerCaseIdentifiers()? _roleTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_roleTableName.toUpperCase(): _roleTableName));
+ tableName = (metaData.storesLowerCaseIdentifiers()? _roleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_roleTableName.toUpperCase(Locale.ENGLISH): _roleTableName));
result = metaData.getTables(null, null, tableName, null);
if (!result.next())
{
@@ -448,7 +449,7 @@ public class DataSourceLoginService extends MappedLoginService
result.close();
- tableName = (metaData.storesLowerCaseIdentifiers()? _userRoleTableName.toLowerCase(): (metaData.storesUpperCaseIdentifiers()?_userRoleTableName.toUpperCase(): _userRoleTableName));
+ tableName = (metaData.storesLowerCaseIdentifiers()? _userRoleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userRoleTableName.toUpperCase(Locale.ENGLISH): _userRoleTableName));
result = metaData.getTables(null, null, tableName, null);
if (!result.next())
{
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
index 18c8e72ea32..f6bb52fc37d 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
@@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
@@ -489,7 +490,7 @@ public class ProxyServlet implements Servlet
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
{
String nameString = name.toString();
- String s = nameString.toLowerCase();
+ String s = nameString.toLowerCase(Locale.ENGLISH);
if (!_DontProxyHeaders.contains(s) || (HttpHeader.CONNECTION.is(name) && HttpHeaderValue.CLOSE.is(value)))
{
if (debug != 0)
@@ -560,7 +561,7 @@ public class ProxyServlet implements Servlet
String connectionHdr = request.getHeader("Connection");
if (connectionHdr != null)
{
- connectionHdr = connectionHdr.toLowerCase();
+ connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH);
if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
connectionHdr = null;
}
@@ -578,7 +579,7 @@ public class ProxyServlet implements Servlet
{
// TODO could be better than this!
String hdr = (String)enm.nextElement();
- String lhdr = hdr.toLowerCase();
+ String lhdr = hdr.toLowerCase(Locale.ENGLISH);
if (_DontProxyHeaders.contains(lhdr))
continue;
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java
index bb80dcd90a3..037dd025f5c 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.LinkedHashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -105,7 +106,7 @@ public abstract class AbstractConnectHandlerTest
assertTrue(header.lookingAt());
String headerName = header.group(1);
String headerValue = header.group(2);
- headers.put(headerName.toLowerCase(), headerValue.toLowerCase());
+ headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
}
StringBuilder body;
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
index 91e3efc69bf..66866e3ff1e 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
@@ -79,6 +79,20 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
@Test
public void test() throws Exception
{
+ _response.setStatus(200);
+ _request.setHandled(false);
+ _handler.setOriginalPathAttribute("/before");
+ _handler.setRewriteRequestURI(true);
+ _handler.setRewritePathInfo(true);
+ _request.setRequestURI("/xxx/bar");
+ _request.setPathInfo("/xxx/bar");
+ _handler.handle("/xxx/bar",_request,_request, _response);
+ assertEquals(201,_response.getStatus());
+ assertEquals("/bar/zzz",_request.getAttribute("target"));
+ assertEquals("/bar/zzz",_request.getAttribute("URI"));
+ assertEquals("/bar/zzz",_request.getAttribute("info"));
+ assertEquals(null,_request.getAttribute("before"));
+
_response.setStatus(200);
_request.setHandled(false);
_handler.setOriginalPathAttribute("/before");
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
index 6ee6bb4e706..c08e97c7b8a 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.security.authentication;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@@ -413,7 +414,7 @@ public class FormAuthenticator extends LoginAuthenticator
@Override
public long getDateHeader(String name)
{
- if (name.toLowerCase().startsWith("if-"))
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return -1;
return super.getDateHeader(name);
}
@@ -421,7 +422,7 @@ public class FormAuthenticator extends LoginAuthenticator
@Override
public String getHeader(String name)
{
- if (name.toLowerCase().startsWith("if-"))
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return null;
return super.getHeader(name);
}
@@ -435,7 +436,7 @@ public class FormAuthenticator extends LoginAuthenticator
@Override
public Enumeration getHeaders(String name)
{
- if (name.toLowerCase().startsWith("if-"))
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
return Collections.enumeration(Collections.emptyList());
return super.getHeaders(name);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index a61f780c133..15d1787ffd1 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -314,7 +315,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
synchronized (_factories)
{
- return _factories.get(protocol.toLowerCase());
+ return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
}
}
@@ -337,7 +338,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
ConnectionFactory old=_factories.remove(factory.getProtocol());
if (old!=null)
removeBean(old);
- _factories.put(factory.getProtocol().toLowerCase(), factory);
+ _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
addBean(factory);
if (_defaultProtocol==null)
_defaultProtocol=factory.getProtocol();
@@ -348,7 +349,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
synchronized (_factories)
{
- ConnectionFactory factory= _factories.remove(protocol.toLowerCase());
+ ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
removeBean(factory);
return factory;
}
@@ -403,7 +404,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void setDefaultProtocol(String defaultProtocol)
{
- _defaultProtocol = defaultProtocol.toLowerCase();
+ _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
if (isRunning())
_defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
index 57e3f042024..6c9472149f3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
@@ -17,6 +17,8 @@
//
package org.eclipse.jetty.server;
+import java.util.Locale;
+
import javax.servlet.http.Cookie;
import org.eclipse.jetty.util.LazyList;
@@ -283,7 +285,7 @@ public class CookieCutter
{
if (name.startsWith("$"))
{
- String lowercaseName = name.toLowerCase();
+ String lowercaseName = name.toLowerCase(Locale.ENGLISH);
if ("$path".equals(lowercaseName))
{
if (cookie!=null)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
index 61b7b983bf5..c7614071991 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
@@ -34,6 +34,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
@@ -124,7 +125,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
public DatabaseAdaptor (DatabaseMetaData dbMeta)
throws SQLException
{
- _dbName = dbMeta.getDatabaseProductName().toLowerCase();
+ _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
LOG.debug ("Using database {}",_dbName);
_isLower = dbMeta.storesLowerCaseIdentifiers();
_isUpper = dbMeta.storesUpperCaseIdentifiers();
@@ -140,9 +141,9 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
public String convertIdentifier (String identifier)
{
if (_isLower)
- return identifier.toLowerCase();
+ return identifier.toLowerCase(Locale.ENGLISH);
if (_isUpper)
- return identifier.toUpperCase();
+ return identifier.toUpperCase(Locale.ENGLISH);
return identifier;
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
index a636570871e..22463973e60 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
+import java.util.Locale;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.IO;
@@ -52,7 +53,7 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
_handler.setSuspendFor(100);
_handler.setResumeAfter(25);
- assertTrue(process(null).toUpperCase().contains("RESUMED"));
+ assertTrue(process(null).toUpperCase(Locale.ENGLISH).contains("RESUMED"));
}
@Test
@@ -66,7 +67,7 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
_server.start();
_handler.setSuspendFor(50);
- assertTrue(process(null).toUpperCase().contains("TIMEOUT"));
+ assertTrue(process(null).toUpperCase(Locale.ENGLISH).contains("TIMEOUT"));
}
@Test
@@ -81,7 +82,7 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
_handler.setSuspendFor(100);
_handler.setCompleteAfter(25);
- assertTrue(process(null).toUpperCase().contains("COMPLETED"));
+ assertTrue(process(null).toUpperCase(Locale.ENGLISH).contains("COMPLETED"));
}
private synchronized String process(String content) throws UnsupportedEncodingException, IOException, InterruptedException
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
index 287ae0d3750..5812e0a2af3 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
@@ -30,6 +30,7 @@ import java.net.Socket;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -153,7 +154,7 @@ public class IPAccessHandlerTest
assertTrue(header.lookingAt());
String headerName = header.group(1);
String headerValue = header.group(2);
- headers.put(headerName.toLowerCase(), headerValue.toLowerCase());
+ headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
}
StringBuilder body = new StringBuilder();
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
index 86e60b356c7..21fe1c763c7 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -90,7 +91,7 @@ public class Invoker extends HttpServlet
{
String param=(String)e.nextElement();
String value=getInitParameter(param);
- String lvalue=value.toLowerCase();
+ String lvalue=value.toLowerCase(Locale.ENGLISH);
if ("nonContextServlets".equals(param))
{
_nonContextServlets=value.length()>0 && lvalue.startsWith("t");
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
index d3cc37bde38..13f9564c5f5 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -138,7 +139,7 @@ public class CGI extends HttpServlet
if (!_env.envMap.containsKey("SystemRoot"))
{
String os = System.getProperty("os.name");
- if (os != null && os.toLowerCase().indexOf("windows") != -1)
+ if (os != null && os.toLowerCase(Locale.ENGLISH).indexOf("windows") != -1)
{
_env.set("SystemRoot","C:\\WINDOWS");
}
@@ -255,7 +256,7 @@ public class CGI extends HttpServlet
{
String name = (String)enm.nextElement();
String value = req.getHeader(name);
- env.set("HTTP_" + name.toUpperCase().replace('-','_'),value);
+ env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
}
// these extra ones were from printenv on www.dev.nomura.co.uk
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
index 81a05fcbf80..5e2b16771de 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.servlets;
import java.io.IOException;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
@@ -277,7 +278,7 @@ public class GzipFilter extends UserAgentFilter
{
for (int i=0; i< encodings.length; i++)
{
- if (encodings[i].toLowerCase().contains(GZIP))
+ if (encodings[i].toLowerCase(Locale.ENGLISH).contains(GZIP))
{
if (isEncodingAcceptable(encodings[i]))
{
@@ -286,7 +287,7 @@ public class GzipFilter extends UserAgentFilter
}
}
- if (encodings[i].toLowerCase().contains(DEFLATE))
+ if (encodings[i].toLowerCase(Locale.ENGLISH).contains(DEFLATE))
{
if (isEncodingAcceptable(encodings[i]))
{
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
index 13a749ca83d..081457e7adc 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -31,6 +31,7 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
index 24b8718a14d..ae2c7baa8ae 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
@@ -26,6 +26,7 @@ import java.net.URL;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletResponse;
@@ -62,7 +63,7 @@ public class PutFilterTest
FilterHolder holder = tester.addFilter(PutFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
holder.setInitParameter("delAllowed","true");
// Bloody Windows does not allow file renaming
- if (!System.getProperty("os.name").toLowerCase().contains("windows"))
+ if (!System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"))
holder.setInitParameter("putAtomic","true");
tester.start();
}
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 c7fda0a6821..3018c7aad6d 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
@@ -95,6 +95,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
catch (IOException x)
{
LOG.debug(x);
+ NextProtoNego.remove(engine);
getEndPoint().close();
return -1;
}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
index 30c3e50a8dc..6ae94e121dc 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.spdy.generator;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
+import java.util.Locale;
import org.eclipse.jetty.spdy.CompressionDictionary;
import org.eclipse.jetty.spdy.CompressionFactory;
@@ -45,7 +46,7 @@ public class HeadersBlockGenerator
writeCount(version, buffer, headers.size());
for (Fields.Field header : headers)
{
- String name = header.name().toLowerCase();
+ String name = header.name().toLowerCase(Locale.ENGLISH);
byte[] nameBytes = name.getBytes(iso1);
writeNameLength(version, buffer, nameBytes.length);
buffer.write(nameBytes, 0, nameBytes.length);
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
index 9f8bdb55a06..13dbe6eb0b6 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -206,7 +207,7 @@ public class ReferrerPushStrategy implements PushStrategy
if (header == null)
return true;
- String contentType = header.value().toLowerCase();
+ String contentType = header.value().toLowerCase(Locale.ENGLISH);
for (String pushContentType : pushContentTypes)
if (contentType.startsWith(pushContentType))
return true;
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
index 10bd415cd1c..585e5f51e9d 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.spdy.server.proxy;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -92,7 +93,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse
@Override
public boolean parsedHeader(HttpHeader header, String headerName, String headerValue)
{
- switch (headerName.toLowerCase())
+ switch (headerName.toLowerCase(Locale.ENGLISH))
{
case "host":
headers.put(HTTPSPDYHeader.HOST.name(version), headerValue);
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 40731ad4443..84d361db069 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
@@ -25,7 +25,6 @@ import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
@@ -93,6 +92,7 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
catch (IOException x)
{
LOG.debug(x);
+ NextProtoNego.remove(engine);
getEndPoint().close();
return -1;
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
index 0235a760cfc..f6249ccaf61 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
@@ -38,6 +38,7 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -269,7 +270,7 @@ public class Config
}
else
{
- String name = entry.getName().toLowerCase();
+ String name = entry.getName().toLowerCase(Locale.ENGLISH);
if (name.endsWith(".jar") || name.endsWith(".zip"))
{
String jar = entry.getCanonicalPath();
@@ -796,7 +797,7 @@ public class Config
}
// Add XML configuration
- if (subject.toLowerCase().endsWith(".xml"))
+ if (subject.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
{
// Config file
File f = new File(fixPath(file));
@@ -807,7 +808,7 @@ public class Config
}
// Set the main class to execute (overrides any previously set)
- if (subject.toLowerCase().endsWith(".class"))
+ if (subject.toLowerCase(Locale.ENGLISH).endsWith(".class"))
{
// Class
String cn = expand(subject.substring(0,subject.length() - 6));
@@ -820,7 +821,7 @@ public class Config
}
// Add raw classpath entry
- if (subject.toLowerCase().endsWith(".path"))
+ if (subject.toLowerCase(Locale.ENGLISH).endsWith(".path"))
{
// classpath (jetty.class.path?) to add to runtime classpath
String cn = expand(subject.substring(0,subject.length() - 5));
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index a13e53c11da..3a9e1d93c30 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -46,6 +46,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Properties;
import java.util.Set;
@@ -365,7 +366,7 @@ public class Main
return false;
}
- String name = path.getName().toLowerCase();
+ String name = path.getName().toLowerCase(Locale.ENGLISH);
return (name.startsWith("jetty") && name.endsWith(".xml"));
}
});
@@ -639,7 +640,7 @@ public class Main
private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
{
- if (!xmlFilename.toLowerCase().endsWith(".xml"))
+ if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
{
// Nothing to resolve.
return xmlFilename;
@@ -853,7 +854,7 @@ public class Main
if (element.isFile())
{
- String name = element.getName().toLowerCase();
+ String name = element.getName().toLowerCase(Locale.ENGLISH);
if (name.endsWith(".jar"))
{
return JarVersion.getVersion(element);
@@ -1105,7 +1106,7 @@ public class Main
@Override
public boolean accept(File dir, String name)
{
- return name.toLowerCase().endsWith(".ini");
+ return name.toLowerCase(Locale.ENGLISH).endsWith(".ini");
}
});
Arrays.sort(inis);
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
index 70e73e97786..f2e823deab9 100755
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -89,9 +90,9 @@ public class JSONObjectConvertor implements JSON.Convertor
{
String name=m.getName();
if (name.startsWith("is"))
- name=name.substring(2,3).toLowerCase()+name.substring(3);
+ name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3);
else if (name.startsWith("get"))
- name=name.substring(3,4).toLowerCase()+name.substring(4);
+ name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
else
continue;
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
index d49d4cfe8ec..b0196fc49b5 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -122,9 +123,9 @@ public class JSONPojoConvertor implements JSON.Convertor
if(m.getReturnType()!=null)
{
if (name.startsWith("is") && name.length()>2)
- name=name.substring(2,3).toLowerCase()+name.substring(3);
+ name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3);
else if (name.startsWith("get") && name.length()>3)
- name=name.substring(3,4).toLowerCase()+name.substring(4);
+ name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
else
break;
if(includeField(name, m))
@@ -134,7 +135,7 @@ public class JSONPojoConvertor implements JSON.Convertor
case 1:
if (name.startsWith("set") && name.length()>3)
{
- name=name.substring(3,4).toLowerCase()+name.substring(4);
+ name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
if(includeField(name, m))
addSetter(name, m);
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
index 83b258b8c9f..18a2d30082c 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -93,7 +94,7 @@ public class Fields implements Iterable
*/
public Field get(String name)
{
- return fields.get(name.trim().toLowerCase());
+ return fields.get(name.trim().toLowerCase(Locale.ENGLISH));
}
/**
@@ -107,7 +108,7 @@ public class Fields implements Iterable
name = name.trim();
// Preserve the case for the field name
Field field = new Field(name, value);
- fields.put(name.toLowerCase(), field);
+ fields.put(name.toLowerCase(Locale.ENGLISH), field);
}
/**
@@ -118,7 +119,7 @@ public class Fields implements Iterable
public void put(Field field)
{
if (field != null)
- fields.put(field.name().toLowerCase(), field);
+ fields.put(field.name().toLowerCase(Locale.ENGLISH), field);
}
/**
@@ -131,16 +132,16 @@ public class Fields implements Iterable
public void add(String name, String value)
{
name = name.trim();
- Field field = fields.get(name.toLowerCase());
+ Field field = fields.get(name.toLowerCase(Locale.ENGLISH));
if (field == null)
{
field = new Field(name, value);
- fields.put(name.toLowerCase(), field);
+ fields.put(name.toLowerCase(Locale.ENGLISH), field);
}
else
{
field = new Field(field.name(), field.values(), value);
- fields.put(name.toLowerCase(), field);
+ fields.put(name.toLowerCase(Locale.ENGLISH), field);
}
}
@@ -153,7 +154,7 @@ public class Fields implements Iterable
public Field remove(String name)
{
name = name.trim();
- return fields.remove(name.toLowerCase());
+ return fields.remove(name.toLowerCase(Locale.ENGLISH));
}
/**
@@ -234,7 +235,7 @@ public class Fields implements Iterable
@Override
public int hashCode()
{
- int result = name.toLowerCase().hashCode();
+ int result = name.toLowerCase(Locale.ENGLISH).hashCode();
result = 31 * result + Arrays.hashCode(values);
return result;
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
index d070687b86a..2d3d004ceb2 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
@@ -176,7 +177,7 @@ public class MultiPartInputStream
{
if (name == null)
return null;
- return (String)_headers.getValue(name.toLowerCase(), 0);
+ return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
}
/**
@@ -500,7 +501,7 @@ public class MultiPartInputStream
int c=line.indexOf(':',0);
if(c>0)
{
- String key=line.substring(0,c).trim().toLowerCase();
+ String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
String value=line.substring(c+1,line.length()).trim();
headers.put(key, value);
if (key.equalsIgnoreCase("content-disposition"))
@@ -526,7 +527,7 @@ public class MultiPartInputStream
while(tok.hasMoreTokens())
{
String t=tok.nextToken().trim();
- String tl=t.toLowerCase();
+ String tl=t.toLowerCase(Locale.ENGLISH);
if(t.startsWith("form-data"))
form_data=true;
else if(tl.startsWith("name="))
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java
index a4d7ee7a13e..fffada8bd33 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java
@@ -27,6 +27,7 @@ import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.Locale;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
@@ -221,7 +222,7 @@ public class RolloverFileOutputStream extends FilterOutputStream
// Is this a rollover file?
String filename=file.getName();
- int i=filename.toLowerCase().indexOf(YYYY_MM_DD);
+ int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
if (i>=0)
{
file=new File(dir,
@@ -258,7 +259,7 @@ public class RolloverFileOutputStream extends FilterOutputStream
File file= new File(_filename);
File dir = new File(file.getParent());
String fn=file.getName();
- int s=fn.toLowerCase().indexOf(YYYY_MM_DD);
+ int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
if (s<0)
return;
String prefix=fn.substring(0,s);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
index b86794899ec..25661424528 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
@@ -700,4 +700,27 @@ public class StringUtil
return minus?(-val):val;
throw new NumberFormatException(string);
}
+
+ /**
+ * Truncate a string to a max size.
+ *
+ * @param str the string to possibly truncate
+ * @param maxSize the maximum size of the string
+ * @return the truncated string. if str param is null, then the returned string will also be null.
+ */
+ public static String truncate(String str, int maxSize)
+ {
+ if (str == null)
+ {
+ return null;
+ }
+
+ if (str.length() <= maxSize)
+ {
+ return str;
+ }
+
+ return str.substring(0,maxSize);
+ }
+
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
index 63ec5b4ca76..f402b3191c9 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
@@ -81,8 +81,6 @@ public class ClasspathPattern
/* ------------------------------------------------------------ */
/**
- * Initialize the matcher by parsing each classpath pattern in an array
- *
* @param patterns array of classpath patterns
*/
private void addPatterns(String[] patterns)
@@ -93,7 +91,8 @@ public class ClasspathPattern
for (String pattern : patterns)
{
entry = createEntry(pattern);
- if (entry != null) {
+ if (entry != null)
+ {
_patterns.add(pattern);
_entries.add(entry);
}
@@ -101,6 +100,29 @@ public class ClasspathPattern
}
}
+ /* ------------------------------------------------------------ */
+ /**
+ * @param patterns array of classpath patterns
+ */
+ private void prependPatterns(String[] patterns)
+ {
+ if (patterns != null)
+ {
+ Entry entry = null;
+ int i=0;
+ for (String pattern : patterns)
+ {
+ entry = createEntry(pattern);
+ if (entry != null)
+ {
+ _patterns.add(i,pattern);
+ _entries.add(i,entry);
+ i++;
+ }
+ }
+ }
+ }
+
/* ------------------------------------------------------------ */
/**
* Create an entry object containing information about
@@ -156,9 +178,24 @@ public class ClasspathPattern
patterns.add(entries.nextToken());
}
- addPatterns((String[])patterns.toArray(new String[patterns.size()]));
+ addPatterns(patterns.toArray(new String[patterns.size()]));
}
+
+ /* ------------------------------------------------------------ */
+ public void prependPattern(String classOrPackage)
+ {
+ ArrayList patterns = new ArrayList();
+ StringTokenizer entries = new StringTokenizer(classOrPackage, ":,");
+ while (entries.hasMoreTokens())
+ {
+ patterns.add(entries.nextToken());
+ }
+
+ prependPatterns(patterns.toArray(new String[patterns.size()]));
+ }
+
+
/* ------------------------------------------------------------ */
/**
* @return array of classpath patterns
@@ -217,4 +254,5 @@ public class ClasspathPattern
}
return result;
}
+
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java
index 4528e162923..0dfa1eefb9e 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JarScanner.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
+import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
@@ -144,7 +145,7 @@ public abstract class JarScanner extends org.eclipse.jetty.util.PatternMatcher
throws Exception
{
LOG.debug("Search of {}",uri);
- if (uri.toString().toLowerCase().endsWith(".jar"))
+ if (uri.toString().toLowerCase(Locale.ENGLISH).endsWith(".jar"))
{
InputStream in = Resource.newResource(uri).getInputStream();
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
index cd55f50d43e..64f2dbab87a 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
@@ -22,6 +22,7 @@ package org.eclipse.jetty.webapp;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.jar.JarEntry;
import org.eclipse.jetty.util.log.Log;
@@ -136,7 +137,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
}
else
{
- String lcname = name.toLowerCase();
+ String lcname = name.toLowerCase(Locale.ENGLISH);
if (lcname.endsWith(".tld"))
{
addResource(context,METAINF_TLDS,Resource.newResource("jar:"+jarUri+"!/"+name));
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
index 11d0a72565c..ef8681f5751 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
@@ -25,6 +25,7 @@ import java.util.EventListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.DispatcherType;
@@ -324,7 +325,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
XmlParser.Node startup = node.get("load-on-startup");
if (startup != null)
{
- String s = startup.toString(false, true).toLowerCase();
+ String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
int order = 0;
if (s.startsWith("t"))
{
@@ -1387,7 +1388,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
if (data != null)
{
data = data.get("transport-guarantee");
- String guarantee = data.toString(false, true).toUpperCase();
+ String guarantee = data.toString(false, true).toUpperCase(Locale.ENGLISH);
if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee))
scBase.setDataConstraint(Constraint.DC_NONE);
else if ("INTEGRAL".equals(guarantee))
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
index 72075ddbe10..c1f7f9a8e78 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.Servlet;
@@ -216,7 +217,7 @@ public class TagLibConfiguration extends AbstractConfiguration
while(iter.hasNext())
{
String location = iter.next();
- if (location!=null && location.toLowerCase().endsWith(".tld"))
+ if (location!=null && location.toLowerCase(Locale.ENGLISH).endsWith(".tld"))
{
if (!location.startsWith("/"))
location="/WEB-INF/"+location;
@@ -233,7 +234,7 @@ public class TagLibConfiguration extends AbstractConfiguration
String[] contents = web_inf.list();
for (int i=0;contents!=null && i
+ *
+ *
+ * @throws IOException
+ */
+ @Test
+ @Ignore("Bug with Transfer-Encoding")
+ public void testTomcat7_0_32_WithTransferEncoding() throws Exception {
+ DummyServer server = new DummyServer();
+ int bufferSize = 512;
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ WebSocketClientFactory factory = new WebSocketClientFactory(threadPool, new ZeroMaskGen(), bufferSize);
+
+ try {
+ server.start();
+
+ // Setup Client Factory
+ threadPool.start();
+ factory.start();
+
+ // Create Client
+ WebSocketClient client = new WebSocketClient(factory);
+
+ // Create End User WebSocket Class
+ final CountDownLatch openLatch = new CountDownLatch(1);
+ final CountDownLatch dataLatch = new CountDownLatch(1);
+ WebSocket.OnTextMessage websocket = new WebSocket.OnTextMessage()
+ {
+ public void onOpen(Connection connection)
+ {
+ openLatch.countDown();
+ }
+
+ public void onMessage(String data)
+ {
+ // System.out.println("data = " + data);
+ dataLatch.countDown();
+ }
+
+ public void onClose(int closeCode, String message)
+ {
+ }
+ };
+
+ // Open connection
+ URI wsURI = server.getWsUri();
+ client.open(wsURI, websocket);
+
+ // Accept incoming connection
+ ServerConnection socket = server.accept();
+ socket.setSoTimeout(2000); // timeout
+
+ // Issue upgrade
+ Map extraResponseHeaders = new HashMap();
+ extraResponseHeaders.put("Transfer-Encoding", "chunked"); // !! The problem !!
+ socket.upgrade(extraResponseHeaders);
+
+ // Wait for proper upgrade
+ Assert.assertTrue("Timed out waiting for Client side WebSocket open event", openLatch.await(1, TimeUnit.SECONDS));
+
+ // Have server write frame.
+ int length = bufferSize / 2;
+ ByteBuffer serverFrame = ByteBuffer.allocate(bufferSize);
+ serverFrame.put((byte)(0x80 | 0x01)); // FIN + TEXT
+ serverFrame.put((byte)0x7E); // No MASK and 2 bytes length
+ serverFrame.put((byte)(length >> 8)); // first length byte
+ serverFrame.put((byte)(length & 0xFF)); // second length byte
+ for (int i = 0; i < length; ++i)
+ serverFrame.put((byte)'x');
+ serverFrame.flip();
+ byte buf[] = serverFrame.array();
+ socket.write(buf,0,buf.length);
+ socket.flush();
+
+ Assert.assertTrue(dataLatch.await(1000, TimeUnit.SECONDS));
+ } finally {
+ factory.stop();
+ threadPool.stop();
+ server.stop();
+ }
+ }
+}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
new file mode 100644
index 00000000000..1a9ff16fcd4
--- /dev/null
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
@@ -0,0 +1,309 @@
+//
+// ========================================================================
+// 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.dummy;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455;
+import org.junit.Assert;
+
+/**
+ * Simple ServerSocket server used to test oddball server scenarios encountered in the real world.
+ */
+public class DummyServer
+{
+ public static class ServerConnection
+ {
+ private static final Logger LOG = Log.getLogger(ServerConnection.class);
+ private final Socket socket;
+ private InputStream in;
+ private OutputStream out;
+
+ public ServerConnection(Socket socket)
+ {
+ this.socket = socket;
+ }
+
+ public int read(ByteBuffer buf) throws IOException
+ {
+ int len = 0;
+ while ((in.available() > 0) && (buf.remaining() > 0))
+ {
+ buf.put((byte)in.read());
+ len++;
+ }
+ return len;
+ }
+
+ public void disconnect()
+ {
+ LOG.debug("disconnect");
+ IO.close(in);
+ IO.close(out);
+ if (socket != null)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+ }
+
+ public InputStream getInputStream() throws IOException
+ {
+ if (in == null)
+ {
+ in = socket.getInputStream();
+ }
+ return in;
+ }
+
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (out == null)
+ {
+ out = socket.getOutputStream();
+ }
+ return out;
+ }
+
+ public void flush() throws IOException
+ {
+ LOG.debug("flush()");
+ getOutputStream().flush();
+ }
+
+ public String readRequest() throws IOException
+ {
+ LOG.debug("Reading client request");
+ StringBuilder request = new StringBuilder();
+ BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+ for (String line = in.readLine(); line != null; line = in.readLine())
+ {
+ if (line.length() == 0)
+ {
+ break;
+ }
+ request.append(line).append("\r\n");
+ LOG.debug("read line: {}",line);
+ }
+
+ LOG.debug("Client Request:{}{}","\n",request);
+ return request.toString();
+ }
+
+ public void respond(String rawstr) throws IOException
+ {
+ LOG.debug("respond(){}{}","\n",rawstr);
+ getOutputStream().write(rawstr.getBytes());
+ flush();
+ }
+
+ public void setSoTimeout(int ms) throws SocketException
+ {
+ socket.setSoTimeout(ms);
+ }
+
+ public void upgrade(Map extraResponseHeaders) throws IOException
+ {
+ @SuppressWarnings("unused")
+ Pattern patExts = Pattern.compile("^Sec-WebSocket-Extensions: (.*)$",Pattern.CASE_INSENSITIVE);
+ Pattern patKey = Pattern.compile("^Sec-WebSocket-Key: (.*)$",Pattern.CASE_INSENSITIVE);
+
+ LOG.debug("(Upgrade) Reading HTTP Request");
+ Matcher mat;
+ String key = "not sent";
+ BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+ for (String line = in.readLine(); line != null; line = in.readLine())
+ {
+ if (line.length() == 0)
+ {
+ break;
+ }
+
+ // TODO: Check for extensions
+ // mat = patExts.matcher(line);
+ // if (mat.matches())
+
+ // Check for Key
+ mat = patKey.matcher(line);
+ if (mat.matches())
+ {
+ key = mat.group(1);
+ }
+ }
+
+ LOG.debug("(Upgrade) Writing HTTP Response");
+ // TODO: handle extensions?
+
+ // Setup Response
+ StringBuilder resp = new StringBuilder();
+ resp.append("HTTP/1.1 101 Upgrade\r\n");
+ resp.append("Upgrade: websocket\r\n");
+ resp.append("Connection: upgrade\r\n");
+ resp.append("Sec-WebSocket-Accept: ");
+ resp.append(WebSocketConnectionRFC6455.hashKey(key)).append("\r\n");
+ // extra response headers.
+ if (extraResponseHeaders != null)
+ {
+ for (Map.Entry header : extraResponseHeaders.entrySet())
+ {
+ resp.append(header.getKey());
+ resp.append(": ");
+ resp.append(header.getValue());
+ resp.append("\r\n");
+ }
+ }
+ resp.append("\r\n");
+
+ // Write Response
+ getOutputStream().write(resp.toString().getBytes());
+ flush();
+ }
+
+ public void write(byte[] bytes) throws IOException
+ {
+ LOG.debug("Writing {} bytes", bytes.length);
+ getOutputStream().write(bytes);
+ }
+
+ public void write(byte[] buf, int offset, int length) throws IOException
+ {
+ LOG.debug("Writing bytes[{}], offset={}, length={}", buf.length, offset, length);
+ getOutputStream().write(buf,offset,length);
+ }
+
+ public void write(int b) throws IOException
+ {
+ LOG.debug("Writing int={}", b);
+ getOutputStream().write(b);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(DummyServer.class);
+ private ServerSocket serverSocket;
+ private URI wsUri;
+
+ public ServerConnection accept() throws IOException
+ {
+ LOG.debug(".accept()");
+ assertIsStarted();
+ Socket socket = serverSocket.accept();
+ return new ServerConnection(socket);
+ }
+
+ private void assertIsStarted()
+ {
+ Assert.assertThat("ServerSocket",serverSocket,notNullValue());
+ Assert.assertThat("ServerSocket.isBound",serverSocket.isBound(),is(true));
+ Assert.assertThat("ServerSocket.isClosed",serverSocket.isClosed(),is(false));
+
+ Assert.assertThat("WsUri",wsUri,notNullValue());
+ }
+
+ public URI getWsUri()
+ {
+ return wsUri;
+ }
+
+ public void respondToClient(Socket connection, String serverResponse) throws IOException
+ {
+ InputStream in = null;
+ InputStreamReader isr = null;
+ BufferedReader buf = null;
+ OutputStream out = null;
+ try
+ {
+ in = connection.getInputStream();
+ isr = new InputStreamReader(in);
+ buf = new BufferedReader(isr);
+ String line;
+ while ((line = buf.readLine()) != null)
+ {
+ // System.err.println(line);
+ if (line.length() == 0)
+ {
+ // Got the "\r\n" line.
+ break;
+ }
+ }
+
+ // System.out.println("[Server-Out] " + serverResponse);
+ out = connection.getOutputStream();
+ out.write(serverResponse.getBytes());
+ out.flush();
+ }
+ finally
+ {
+ IO.close(buf);
+ IO.close(isr);
+ IO.close(in);
+ IO.close(out);
+ }
+ }
+
+ public void start() throws IOException
+ {
+ serverSocket = new ServerSocket();
+ InetAddress addr = InetAddress.getByName("localhost");
+ InetSocketAddress endpoint = new InetSocketAddress(addr,0);
+ serverSocket.bind(endpoint);
+ int port = serverSocket.getLocalPort();
+ String uri = String.format("ws://%s:%d/",addr.getHostAddress(),port);
+ wsUri = URI.create(uri);
+ LOG.debug("Server Started on {} -> {}",endpoint,wsUri);
+ }
+
+ public void stop()
+ {
+ LOG.debug("Stopping Server");
+ try
+ {
+ serverSocket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+
+}
diff --git a/jetty-websocket/src/test/resources/jetty-logging.properties b/jetty-websocket/src/test/resources/jetty-logging.properties
new file mode 100644
index 00000000000..78d8a9d8123
--- /dev/null
+++ b/jetty-websocket/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+# Setup default logging implementation for during testing
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=INFO
+org.eclipse.jetty.websocket.LEVEL=DEBUG
\ No newline at end of file
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeRequest.java
index b8fe6e3a8d8..6f4961670b8 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeRequest.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeRequest.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
@@ -188,7 +189,7 @@ public class ClientUpgradeRequest implements UpgradeRequest
for (String key : headers.keySet())
{
String value = headers.get(key);
- if (FORBIDDEN_HEADERS.contains(key.toLowerCase()))
+ if (FORBIDDEN_HEADERS.contains(key.toLowerCase(Locale.ENGLISH)))
{
LOG.warn("Skipping forbidden header - {}: {}",key,value);
continue; // skip
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeResponse.java
index 0a3e4020a5a..9f81d18b57b 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeResponse.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ClientUpgradeResponse.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import org.eclipse.jetty.util.MultiMap;
@@ -47,7 +48,7 @@ public class ClientUpgradeResponse implements UpgradeResponse
@Override
public void addHeader(String name, String value)
{
- headers.add(name.toLowerCase(),value);
+ headers.add(name.toLowerCase(Locale.ENGLISH),value);
}
@Override
@@ -71,13 +72,13 @@ public class ClientUpgradeResponse implements UpgradeResponse
@Override
public String getHeaderValue(String name)
{
- return headers.getValue(name.toLowerCase(),0);
+ return headers.getValue(name.toLowerCase(Locale.ENGLISH),0);
}
@Override
public Iterator getHeaderValues(String name)
{
- List values = headers.getValues(name.toLowerCase());
+ List values = headers.getValues(name.toLowerCase(Locale.ENGLISH));
if (values == null)
{
return Collections.emptyIterator();
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ConnectionManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ConnectionManager.java
index 0cb7c902bca..4f818447336 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ConnectionManager.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/ConnectionManager.java
@@ -25,6 +25,7 @@ import java.net.URI;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.Collections;
+import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
@@ -57,7 +58,7 @@ public class ConnectionManager extends ContainerLifeCycle
}
int port = uri.getPort();
- String scheme = uri.getScheme().toLowerCase();
+ String scheme = uri.getScheme().toLowerCase(Locale.ENGLISH);
if ("ws".equals(scheme))
{
if (port == (-1))
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DefaultWebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DefaultWebSocketClient.java
index 6d44cd5805d..001e68ed140 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DefaultWebSocketClient.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DefaultWebSocketClient.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.client.internal;
import java.io.IOException;
import java.net.URI;
+import java.util.Locale;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.StringUtil;
@@ -99,7 +100,7 @@ public class DefaultWebSocketClient extends FutureCallback impl
throw new IllegalArgumentException("WebSocket URI must include a scheme");
}
- String scheme = websocketUri.getScheme().toLowerCase();
+ String scheme = websocketUri.getScheme().toLowerCase(Locale.ENGLISH);
if (("ws".equals(scheme) == false) && ("wss".equals(scheme) == false))
{
throw new IllegalArgumentException("WebSocket URI scheme only supports [ws] and [wss], not [" + scheme + "]");
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 50a2d13301f..3d54f72943d 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
@@ -38,6 +38,7 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
private ExtensionConfig config;
private IncomingFrames nextIncomingFrames;
private OutgoingFrames nextOutgoingFrames;
+ private WebSocketConnection connection;
public ByteBufferPool getBufferPool()
{
@@ -49,6 +50,11 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
return config;
}
+ public WebSocketConnection getConnection()
+ {
+ return connection;
+ }
+
public String getName()
{
return config.getName();
@@ -211,6 +217,11 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
this.config = config;
}
+ public void setConnection(WebSocketConnection connection)
+ {
+ this.connection = connection;
+ }
+
public void setNextIncomingFrames(IncomingFrames nextIncomingFramesHandler)
{
this.nextIncomingFrames = nextIncomingFramesHandler;
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxChannel.java
new file mode 100644
index 00000000000..fed481e733d
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxChannel.java
@@ -0,0 +1,333 @@
+//
+// ========================================================================
+// 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.mux;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.api.BaseConnection.SuspendToken;
+import org.eclipse.jetty.websocket.core.api.Extension;
+import org.eclipse.jetty.websocket.core.api.StatusCode;
+import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
+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.OutgoingFrames;
+import org.eclipse.jetty.websocket.core.io.WebSocketSession;
+import org.eclipse.jetty.websocket.core.protocol.CloseInfo;
+import org.eclipse.jetty.websocket.core.protocol.ConnectionState;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+/**
+ * MuxChannel, acts as WebSocketConnection for specific sub-channel.
+ */
+public class MuxChannel implements WebSocketConnection, IncomingFrames, OutgoingFrames, SuspendToken
+{
+ private static final Logger LOG = Log.getLogger(MuxChannel.class);
+
+ private final long channelId;
+ private final Muxer muxer;
+ private final AtomicBoolean inputClosed;
+ private final AtomicBoolean outputClosed;
+ private final AtomicBoolean suspendToken;
+ private ConnectionState connectionState;
+ private WebSocketPolicy policy;
+ private WebSocketSession session;
+ private IncomingFrames incoming;
+ private String subProtocol;
+
+ public MuxChannel(long channelId, Muxer muxer)
+ {
+ this.channelId = channelId;
+ this.muxer = muxer;
+ this.policy = muxer.getPolicy().clonePolicy();
+
+ this.suspendToken = new AtomicBoolean(false);
+ this.connectionState = ConnectionState.CONNECTING;
+
+ this.inputClosed = new AtomicBoolean(false);
+ this.outputClosed = new AtomicBoolean(false);
+ }
+
+ @Override
+ public void close()
+ {
+ close(StatusCode.NORMAL,null);
+ }
+
+ @Override
+ public void close(int statusCode, String reason)
+ {
+ CloseInfo close = new CloseInfo(statusCode,reason);
+ try
+ {
+ output("",new FutureCallback<>(),close.asFrame());
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Unable to issue Close",e);
+ disconnect();
+ }
+ }
+
+ @Override
+ public void disconnect()
+ {
+ this.connectionState = ConnectionState.CLOSED;
+ // TODO: disconnect the virtual end-point?
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ return muxer.getRemoteAddress();
+ }
+
+ public WebSocketSession getSession()
+ {
+ return session;
+ }
+
+ @Override
+ public ConnectionState getState()
+ {
+ return this.connectionState;
+ }
+
+ @Override
+ public String getSubProtocol()
+ {
+ return this.subProtocol;
+ }
+
+ /**
+ * Incoming exceptions from Muxer.
+ */
+ @Override
+ public void incoming(WebSocketException e)
+ {
+ incoming.incoming(e);
+ }
+
+ /**
+ * Incoming frames from Muxer
+ */
+ @Override
+ public void incoming(WebSocketFrame frame)
+ {
+ incoming.incoming(frame);
+ }
+
+ public boolean isActive()
+ {
+ return (getState() != ConnectionState.CLOSED);
+ }
+
+ @Override
+ public boolean isInputClosed()
+ {
+ return inputClosed.get();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return isActive() && muxer.isOpen();
+ }
+
+ @Override
+ public boolean isOutputClosed()
+ {
+ return outputClosed.get();
+ }
+
+ @Override
+ public boolean isReading()
+ {
+ return true;
+ }
+
+ public void onClose()
+ {
+ this.connectionState = ConnectionState.CLOSED;
+ }
+
+ @Override
+ public void onCloseHandshake(boolean incoming, CloseInfo close)
+ {
+ boolean in = inputClosed.get();
+ boolean out = outputClosed.get();
+ if (incoming)
+ {
+ in = true;
+ this.inputClosed.set(true);
+ }
+ else
+ {
+ out = true;
+ this.outputClosed.set(true);
+ }
+
+ LOG.debug("onCloseHandshake({},{}), input={}, output={}",incoming,close,in,out);
+
+ if (in && out)
+ {
+ LOG.debug("Close Handshake satisfied, disconnecting");
+ this.disconnect();
+ }
+
+ if (close.isHarsh())
+ {
+ LOG.debug("Close status code was harsh, disconnecting");
+ this.disconnect();
+ }
+ }
+
+ public void onOpen()
+ {
+ this.connectionState = ConnectionState.OPEN;
+ }
+
+ /**
+ * Frames destined for the Muxer
+ */
+ @Override
+ public void output(C context, Callback callback, WebSocketFrame frame) throws IOException
+ {
+ muxer.output(context,callback,channelId,frame);
+ }
+
+ /**
+ * Ping frame destined for the Muxer
+ */
+ @Override
+ public void ping(C context, Callback callback, byte[] payload) throws IOException
+ {
+ output(context,callback,WebSocketFrame.ping().setPayload(payload));
+ }
+
+ @Override
+ public void resume()
+ {
+ if (suspendToken.getAndSet(false))
+ {
+ // TODO: Start reading again. (how?)
+ }
+ }
+
+ public void setSession(WebSocketSession session)
+ {
+ this.session = session;
+ this.incoming = session;
+ session.setOutgoing(this);
+ }
+
+ public void setSubProtocol(String subProtocol)
+ {
+ this.subProtocol = subProtocol;
+ }
+
+ @Override
+ public SuspendToken suspend()
+ {
+ suspendToken.set(true);
+ // TODO: how to suspend reading?
+ return this;
+ }
+
+ public void wireUpExtensions(List extensions)
+ {
+ // Start with default routing.
+ incoming = session;
+ OutgoingFrames outgoing = this;
+
+ if (extensions != null)
+ {
+ Iterator extIter;
+ // Connect outgoings
+ extIter = extensions.iterator();
+ while (extIter.hasNext())
+ {
+ Extension ext = extIter.next();
+ ext.setNextOutgoingFrames(outgoing);
+ outgoing = ext;
+ }
+
+ // Connect incomings
+ Collections.reverse(extensions);
+ extIter = extensions.iterator();
+ while (extIter.hasNext())
+ {
+ Extension ext = extIter.next();
+ ext.setNextIncomingFrames(incoming);
+ incoming = ext;
+ }
+ }
+
+ // set outgoing
+ this.session.setOutgoing(outgoing);
+ }
+
+ /**
+ * Generate a binary message, destined for Muxer
+ */
+ @Override
+ public void write(C context, Callback callback, byte[] buf, int offset, int len) throws IOException
+ {
+ output(context,callback,WebSocketFrame.binary().setPayload(buf,offset,len));
+ }
+
+ /**
+ * Generate a binary message, destined for Muxer
+ */
+ @Override
+ public void write(C context, Callback callback, ByteBuffer buffer) throws IOException
+ {
+ output(context,callback,WebSocketFrame.binary().setPayload(buffer));
+ }
+
+ /**
+ * Generate a text message, destined for Muxer
+ */
+ @Override
+ public void write(C context, Callback callback, String message) throws IOException
+ {
+ output(context,callback,WebSocketFrame.text(message));
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxControlBlock.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxControlBlock.java
new file mode 100644
index 00000000000..b617b545b70
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxControlBlock.java
@@ -0,0 +1,24 @@
+//
+// ========================================================================
+// 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.mux;
+
+public interface MuxControlBlock
+{
+ public int getOpCode();
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxException.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxException.java
new file mode 100644
index 00000000000..f7b13cc686a
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxException.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// 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.mux;
+
+import org.eclipse.jetty.websocket.core.api.WebSocketException;
+
+@SuppressWarnings("serial")
+public class MuxException extends WebSocketException
+{
+ public MuxException(String message)
+ {
+ super(message);
+ }
+
+ public MuxException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public MuxException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxExtension.java
new file mode 100644
index 00000000000..0b8fc7040a4
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxExtension.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// 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.mux;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.websocket.core.api.Extension;
+import org.eclipse.jetty.websocket.core.api.WebSocketException;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+/**
+ * Multiplexing Extension for WebSockets.
+ *
+ * Supporting draft-ietf-hybi-websocket-multiplexing-08 Specification.
+ */
+public class MuxExtension extends Extension
+{
+ private Muxer muxer;
+
+ public MuxExtension()
+ {
+ super();
+ }
+
+ public synchronized Muxer getMuxer()
+ {
+ if (this.muxer == null)
+ {
+ this.muxer = new Muxer(super.getConnection(),this);
+ }
+ return muxer;
+ }
+
+ @Override
+ public void incoming(WebSocketException e)
+ {
+ getMuxer().incoming(e);
+ }
+
+ @Override
+ public void incoming(WebSocketFrame frame)
+ {
+ getMuxer().incoming(frame);
+ }
+
+ @Override
+ public void output(C context, Callback callback, WebSocketFrame frame) throws java.io.IOException
+ {
+ nextOutput(context,callback,frame);
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGenerator.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGenerator.java
new file mode 100644
index 00000000000..2950d091c9e
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGenerator.java
@@ -0,0 +1,276 @@
+//
+// ========================================================================
+// 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.mux;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxNewChannelSlot;
+import org.eclipse.jetty.websocket.core.io.OutgoingFrames;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+/**
+ * Generate Mux frames destined for the physical connection.
+ */
+public class MuxGenerator
+{
+ private static final int CONTROL_BUFFER_SIZE = 2 * 1024;
+ /** 4 bytes for channel ID + 1 for fin/rsv/opcode */
+ private static final int DATA_FRAME_OVERHEAD = 5;
+ private ByteBufferPool bufferPool;
+ private OutgoingFrames outgoing;
+
+ public MuxGenerator()
+ {
+ this(new ArrayByteBufferPool());
+ }
+
+ public MuxGenerator(ByteBufferPool bufferPool)
+ {
+ this.bufferPool = bufferPool;
+ }
+
+ public void generate(long channelId, WebSocketFrame frame) throws IOException
+ {
+ output(null, new FutureCallback<>(), channelId, frame);
+ }
+
+ public void generate(MuxControlBlock... blocks) throws IOException
+ {
+ if ((blocks == null) || (blocks.length <= 0))
+ {
+ return; // nothing to do
+ }
+
+ ByteBuffer payload = bufferPool.acquire(CONTROL_BUFFER_SIZE,false);
+ BufferUtil.flipToFill(payload);
+
+ writeChannelId(payload,0); // control channel
+
+ for (MuxControlBlock block : blocks)
+ {
+ switch (block.getOpCode())
+ {
+ case MuxOp.ADD_CHANNEL_REQUEST:
+ {
+ MuxAddChannelRequest op = (MuxAddChannelRequest)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)((op.getRsv() & 0x07) << 2); // rsv
+ b |= (op.getEnc() & 0x03); // enc
+ payload.put(b); // opcode + rsv + enc
+ writeChannelId(payload,op.getChannelId());
+ write139Buffer(payload,op.getHandshake());
+ break;
+ }
+ case MuxOp.ADD_CHANNEL_RESPONSE:
+ {
+ MuxAddChannelResponse op = (MuxAddChannelResponse)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (op.isFailed()?0x10:0x00); // failure bit
+ b |= (byte)((op.getRsv() & 0x03) << 2); // rsv
+ b |= (op.getEnc() & 0x03); // enc
+ payload.put(b); // opcode + f + rsv + enc
+ writeChannelId(payload,op.getChannelId());
+ if (op.getHandshake() != null)
+ {
+ write139Buffer(payload,op.getHandshake());
+ }
+ else
+ {
+ // no handshake details
+ write139Size(payload,0);
+ }
+ break;
+ }
+ case MuxOp.DROP_CHANNEL:
+ {
+ MuxDropChannel op = (MuxDropChannel)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x1F); // rsv
+ payload.put(b); // opcode + rsv
+ writeChannelId(payload,op.getChannelId());
+ write139Buffer(payload,op.asReasonBuffer());
+ break;
+ }
+ case MuxOp.FLOW_CONTROL:
+ {
+ MuxFlowControl op = (MuxFlowControl)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x1F); // rsv
+ payload.put(b); // opcode + rsv
+ writeChannelId(payload,op.getChannelId());
+ write139Size(payload,op.getSendQuotaSize());
+ break;
+ }
+ case MuxOp.NEW_CHANNEL_SLOT:
+ {
+ MuxNewChannelSlot op = (MuxNewChannelSlot)block;
+ byte b = (byte)((op.getOpCode() & 0x07) << 5); // opcode
+ b |= (byte)(op.getRsv() & 0x0F) << 1; // rsv
+ b |= (byte)(op.isFallback()?0x01:0x00); // fallback bit
+ payload.put(b); // opcode + rsv + fallback bit
+ write139Size(payload,op.getNumberOfSlots());
+ write139Size(payload,op.getInitialSendQuota());
+ break;
+ }
+ }
+ }
+ BufferUtil.flipToFlush(payload,0);
+ WebSocketFrame frame = WebSocketFrame.binary();
+ frame.setPayload(payload);
+ outgoing.output(null,new FutureCallback<>(),frame);
+ }
+
+ public OutgoingFrames getOutgoing()
+ {
+ return outgoing;
+ }
+
+ public void output(C context, Callback callback, long channelId, WebSocketFrame frame) throws IOException
+ {
+ ByteBuffer muxPayload = bufferPool.acquire(frame.getPayloadLength() + DATA_FRAME_OVERHEAD,false);
+ BufferUtil.flipToFill(muxPayload);
+
+ // start building mux payload
+ writeChannelId(muxPayload,channelId);
+ byte b = (byte)(frame.isFin()?0x80:0x00); // fin
+ b |= (byte)(frame.isRsv1()?0x40:0x00); // rsv1
+ b |= (byte)(frame.isRsv2()?0x20:0x00); // rsv2
+ b |= (byte)(frame.isRsv3()?0x10:0x00); // rsv3
+ b |= (byte)(frame.getOpCode() & 0x0F); // opcode
+ muxPayload.put(b);
+ BufferUtil.put(frame.getPayload(),muxPayload);
+
+ // build muxed frame
+ WebSocketFrame muxFrame = WebSocketFrame.binary();
+ BufferUtil.flipToFlush(muxPayload,0);
+ muxFrame.setPayload(muxPayload);
+ // NOTE: the physical connection will handle masking rules for this frame.
+
+ // release original buffer (no longer needed)
+ bufferPool.release(frame.getPayload());
+
+ // send muxed frame down to the physical connection.
+ outgoing.output(context,callback,muxFrame);
+ }
+
+ public void setOutgoing(OutgoingFrames outgoing)
+ {
+ this.outgoing = outgoing;
+ }
+
+ /**
+ * Write a 1/3/9 encoded size, then a byte buffer of that size.
+ *
+ * @param payload
+ * @param buffer
+ */
+ public void write139Buffer(ByteBuffer payload, ByteBuffer buffer)
+ {
+ write139Size(payload,buffer.remaining());
+ writeBuffer(payload,buffer);
+ }
+
+ /**
+ * Write a 1/3/9 encoded size.
+ *
+ * @param payload
+ * @param size
+ */
+ public void write139Size(ByteBuffer payload, long size)
+ {
+ if (size > 0xFF_FF)
+ {
+ // 9 byte encoded
+ payload.put((byte)0x7F);
+ payload.putLong(size);
+ return;
+ }
+
+ if (size >= 0x7E)
+ {
+ // 3 byte encoded
+ payload.put((byte)0x7E);
+ payload.put((byte)(size >> 8));
+ payload.put((byte)(size & 0xFF));
+ return;
+ }
+
+ // 1 byte (7 bit) encoded
+ payload.put((byte)(size & 0x7F));
+ }
+
+ public void writeBuffer(ByteBuffer payload, ByteBuffer buffer)
+ {
+ BufferUtil.put(buffer,payload);
+ }
+
+ /**
+ * Write multiplexing channel id, using logical channel id encoding (of 1,2,3, or 4 octets)
+ *
+ * @param payload
+ * @param channelId
+ */
+ public void writeChannelId(ByteBuffer payload, long channelId)
+ {
+ if (channelId > 0x1F_FF_FF_FF)
+ {
+ throw new MuxException("Illegal Channel ID: too big");
+ }
+
+ if (channelId > 0x1F_FF_FF)
+ {
+ // 29 bit channel id (4 bytes)
+ payload.put((byte)(0xE0 | ((channelId >> 24) & 0x1F)));
+ payload.put((byte)((channelId >> 16) & 0xFF));
+ payload.put((byte)((channelId >> 8) & 0xFF));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ if (channelId > 0x3F_FF)
+ {
+ // 21 bit channel id (3 bytes)
+ payload.put((byte)(0xC0 | ((channelId >> 16) & 0x1F)));
+ payload.put((byte)((channelId >> 8) & 0xFF));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ if (channelId > 0x7F)
+ {
+ // 14 bit channel id (2 bytes)
+ payload.put((byte)(0x80 | ((channelId >> 8) & 0x3F)));
+ payload.put((byte)(channelId & 0xFF));
+ return;
+ }
+
+ // 7 bit channel id
+ payload.put((byte)(channelId & 0x7F));
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxOp.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxOp.java
new file mode 100644
index 00000000000..2b2af55e4df
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxOp.java
@@ -0,0 +1,28 @@
+//
+// ========================================================================
+// 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.mux;
+
+public final class MuxOp
+{
+ public static final byte ADD_CHANNEL_REQUEST = 0;
+ public static final byte ADD_CHANNEL_RESPONSE = 1;
+ public static final byte FLOW_CONTROL = 2;
+ public static final byte DROP_CHANNEL = 3;
+ public static final byte NEW_CHANNEL_SLOT = 4;
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParser.java
new file mode 100644
index 00000000000..4e3c2d29a5f
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParser.java
@@ -0,0 +1,409 @@
+//
+// ========================================================================
+// 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.mux;
+
+import java.nio.ByteBuffer;
+
+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.extensions.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxNewChannelSlot;
+import org.eclipse.jetty.websocket.core.protocol.OpCode;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+public class MuxParser
+{
+ public static interface Listener
+ {
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request);
+
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response);
+
+ public void onMuxDropChannel(MuxDropChannel drop);
+
+ public void onMuxedFrame(MuxedFrame frame);
+
+ public void onMuxException(MuxException e);
+
+ public void onMuxFlowControl(MuxFlowControl flow);
+
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot);
+ }
+
+ private final static Logger LOG = Log.getLogger(MuxParser.class);
+
+ private MuxedFrame muxframe = new MuxedFrame();
+ private MuxParser.Listener events;
+ private long channelId;
+
+ public MuxParser.Listener getEvents()
+ {
+ return events;
+ }
+
+ /**
+ * Parse the raw {@link WebSocketFrame} payload data for various Mux frames.
+ *
+ * @param frame
+ * the WebSocketFrame to parse for mux payload
+ */
+ public synchronized void parse(WebSocketFrame frame)
+ {
+ if (events == null)
+ {
+ throw new RuntimeException("No " + MuxParser.Listener.class + " specified");
+ }
+
+ if (!frame.hasPayload())
+ {
+ LOG.debug("No payload data, skipping");
+ return; // nothing to parse
+ }
+
+ if (frame.getOpCode() != OpCode.BINARY)
+ {
+ LOG.debug("Not a binary opcode (base frame), skipping");
+ return; // not a binary opcode
+ }
+
+ LOG.debug("Parsing Mux Payload of {}",frame);
+
+ try
+ {
+ ByteBuffer buffer = frame.getPayload().slice();
+
+ if (buffer.remaining() <= 0)
+ {
+ return;
+ }
+
+ if (frame.isContinuation())
+ {
+ muxframe.reset();
+ muxframe.setFin(frame.isFin());
+ muxframe.setFin(frame.isRsv1());
+ muxframe.setFin(frame.isRsv2());
+ muxframe.setFin(frame.isRsv3());
+ muxframe.setContinuation(true);
+ parseDataFramePayload(buffer);
+ }
+ else
+ {
+ // new frame
+ channelId = readChannelId(buffer);
+ if (channelId == 0)
+ {
+ parseControlBlocks(buffer);
+ }
+ else
+ {
+ parseDataFrame(buffer);
+ }
+ }
+ }
+ catch (MuxException e)
+ {
+ events.onMuxException(e);
+ }
+ catch (Throwable t)
+ {
+ events.onMuxException(new MuxException(t));
+ }
+ }
+
+ private void parseControlBlocks(ByteBuffer buffer)
+ {
+ // process the remaining buffer here.
+ while (buffer.remaining() > 0)
+ {
+ byte b = buffer.get();
+ byte opc = (byte)((byte)(b >> 5) & 0xFF);
+ b = (byte)(b & 0x1F);
+
+ try {
+ switch (opc)
+ {
+ case MuxOp.ADD_CHANNEL_REQUEST:
+ {
+ MuxAddChannelRequest op = new MuxAddChannelRequest();
+ op.setRsv((byte)((b & 0x1C) >> 2));
+ op.setEnc((byte)(b & 0x03));
+ op.setChannelId(readChannelId(buffer));
+ long handshakeSize = read139EncodedSize(buffer);
+ op.setHandshake(readBlock(buffer,handshakeSize));
+ events.onMuxAddChannelRequest(op);
+ break;
+ }
+ case MuxOp.ADD_CHANNEL_RESPONSE:
+ {
+ MuxAddChannelResponse op = new MuxAddChannelResponse();
+ op.setFailed((b & 0x10) != 0);
+ op.setRsv((byte)((byte)(b & 0x0C) >> 2));
+ op.setEnc((byte)(b & 0x03));
+ op.setChannelId(readChannelId(buffer));
+ long handshakeSize = read139EncodedSize(buffer);
+ op.setHandshake(readBlock(buffer,handshakeSize));
+ events.onMuxAddChannelResponse(op);
+ break;
+ }
+ case MuxOp.DROP_CHANNEL:
+ {
+ int rsv = (b & 0x1F);
+ long channelId = readChannelId(buffer);
+ long reasonSize = read139EncodedSize(buffer);
+ ByteBuffer reasonBuf = readBlock(buffer,reasonSize);
+ MuxDropChannel op = MuxDropChannel.parse(channelId,reasonBuf);
+ op.setRsv(rsv);
+ events.onMuxDropChannel(op);
+ break;
+ }
+ case MuxOp.FLOW_CONTROL:
+ {
+ MuxFlowControl op = new MuxFlowControl();
+ op.setRsv((byte)(b & 0x1F));
+ op.setChannelId(readChannelId(buffer));
+ op.setSendQuotaSize(read139EncodedSize(buffer));
+ events.onMuxFlowControl(op);
+ break;
+ }
+ case MuxOp.NEW_CHANNEL_SLOT:
+ {
+ MuxNewChannelSlot op = new MuxNewChannelSlot();
+ op.setRsv((byte)((b & 0x1E) >> 1));
+ op.setFallback((b & 0x01) != 0);
+ op.setNumberOfSlots(read139EncodedSize(buffer));
+ op.setInitialSendQuota(read139EncodedSize(buffer));
+ events.onMuxNewChannelSlot(op);
+ break;
+ }
+ default:
+ {
+ String err = String.format("Unknown Mux Control Code OPC [0x%X]",opc);
+ throw new MuxException(err);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ throw new MuxException(t);
+ }
+ }
+ }
+
+ private void parseDataFrame(ByteBuffer buffer)
+ {
+ byte b = buffer.get();
+ boolean fin = ((b & 0x80) != 0);
+ boolean rsv1 = ((b & 0x40) != 0);
+ boolean rsv2 = ((b & 0x20) != 0);
+ boolean rsv3 = ((b & 0x10) != 0);
+ byte opcode = (byte)(b & 0x0F);
+
+ if (opcode == OpCode.CONTINUATION)
+ {
+ muxframe.setContinuation(true);
+ }
+ else
+ {
+ muxframe.reset();
+ muxframe.setOpCode(opcode);
+ }
+
+ muxframe.setChannelId(channelId);
+ muxframe.setFin(fin);
+ muxframe.setRsv1(rsv1);
+ muxframe.setRsv2(rsv2);
+ muxframe.setRsv3(rsv3);
+
+ parseDataFramePayload(buffer);
+ }
+
+ private void parseDataFramePayload(ByteBuffer buffer)
+ {
+ int capacity = buffer.remaining();
+ ByteBuffer payload = ByteBuffer.allocate(capacity);
+ payload.put(buffer);
+ BufferUtil.flipToFlush(payload,0);
+ muxframe.setPayload(payload);
+ try
+ {
+ LOG.debug("notifyFrame() - {}",muxframe);
+ events.onMuxedFrame(muxframe);
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ }
+ }
+
+ /**
+ * Per section 9.1. Number Encoding in Multiplex Control
+ * Blocks, read the 1/3/9 byte length using Section 5.2 of RFC 6455.
+ *
+ * @param buffer
+ * the buffer to read from
+ * @return the decoded size
+ * @throws MuxException
+ * when the encoding does not make sense per the spec, or it is a value above {@link Long#MAX_VALUE}
+ */
+ public long read139EncodedSize(ByteBuffer buffer)
+ {
+ long ret = -1;
+ long minValue = 0x00; // used to validate minimum # of bytes (per spec)
+ int cursor = 0;
+
+ byte b = buffer.get();
+ ret = (b & 0x7F);
+
+ if (ret == 0x7F)
+ {
+ // 9 byte length
+ ret = 0;
+ minValue = 0xFF_FF;
+ cursor = 8;
+ }
+ else if (ret == 0x7E)
+ {
+ // 3 byte length
+ ret = 0;
+ minValue = 0x7F;
+ cursor = 2;
+ }
+ else
+ {
+ // 1 byte length
+ // no validation of minimum bytes needed here
+ return ret;
+ }
+
+ // parse multi-byte length
+ while (cursor > 0)
+ {
+ ret = ret << 8;
+ b = buffer.get();
+ ret |= (b & 0xFF);
+ --cursor;
+ }
+
+ // validate minimum value per spec.
+ if (ret <= minValue)
+ {
+ String err = String.format("Invalid 1/3/9 length 0x%X (minimum value for chosen encoding is 0x%X)",ret,minValue);
+ throw new MuxException(err);
+ }
+
+ return ret;
+ }
+
+ private ByteBuffer readBlock(ByteBuffer buffer, long size)
+ {
+ if (size == 0)
+ {
+ return null;
+ }
+
+ if (size > buffer.remaining())
+ {
+ String err = String.format("Truncated data, expected %,d byte(s), but only %,d byte(s) remain",size,buffer.remaining());
+ throw new MuxException(err);
+ }
+
+ if (size > Integer.MAX_VALUE)
+ {
+ String err = String.format("[Int-Sane!] Buffer size %,d is too large to be supported (max allowed is %,d)",size,Integer.MAX_VALUE);
+ throw new MuxException(err);
+ }
+
+ ByteBuffer ret = ByteBuffer.allocate((int)size);
+ BufferUtil.put(buffer,ret);
+ BufferUtil.flipToFlush(ret,0);
+ return ret;
+ }
+
+ /**
+ * Read Channel ID using Section 7. Framing techniques
+ *
+ * @param buffer
+ * the buffer to parse from.
+ * @return the channel Id
+ * @throws MuxException
+ * when the encoding does not make sense per the spec.
+ */
+ public long readChannelId(ByteBuffer buffer)
+ {
+ long id = -1;
+ long minValue = 0x00; // used to validate minimum # of bytes (per spec)
+ byte b = buffer.get();
+ int cursor = -1;
+ if ((b & 0x80) == 0)
+ {
+ // 7 bit channel id
+ // no validation of minimum bytes needed here
+ return (b & 0x7F);
+ }
+ else if ((b & 0x40) == 0)
+ {
+ // 14 bit channel id
+ id = (b & 0x3F);
+ minValue = 0x7F;
+ cursor = 1;
+ }
+ else if ((b & 0x20) == 0)
+ {
+ // 21 bit channel id
+ id = (b & 0x1F);
+ minValue = 0x3F_FF;
+ cursor = 2;
+ }
+ else
+ {
+ // 29 bit channel id
+ id = (b & 0x1F);
+ minValue = 0x1F_FF_FF;
+ cursor = 3;
+ }
+
+ while (cursor > 0)
+ {
+ id = id << 8;
+ b = buffer.get();
+ id |= (b & 0xFF);
+ --cursor;
+ }
+
+ // validate minimum value per spec.
+ if (id <= minValue)
+ {
+ String err = String.format("Invalid Channel ID 0x%X (minimum value for chosen encoding is 0x%X)",id,minValue);
+ throw new MuxException(err);
+ }
+
+ return id;
+ }
+
+ public void setEvents(MuxParser.Listener events)
+ {
+ this.events = events;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxPhysicalConnectionException.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxPhysicalConnectionException.java
new file mode 100644
index 00000000000..23567cd2dca
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxPhysicalConnectionException.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// 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.mux;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxDropChannel;
+
+public class MuxPhysicalConnectionException extends MuxException
+{
+ private static final long serialVersionUID = 1L;
+ private MuxDropChannel drop;
+
+ public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase)
+ {
+ super(phrase);
+ drop = new MuxDropChannel(0,code,phrase);
+ }
+
+ public MuxPhysicalConnectionException(MuxDropChannel.Reason code, String phrase, Throwable t)
+ {
+ super(phrase,t);
+ drop = new MuxDropChannel(0,code,phrase);
+ }
+
+ public MuxDropChannel getMuxDropChannel()
+ {
+ return drop;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxedFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxedFrame.java
new file mode 100644
index 00000000000..4d17dafc7d0
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxedFrame.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// 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.mux;
+
+import org.eclipse.jetty.websocket.core.protocol.OpCode;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+public class MuxedFrame extends WebSocketFrame
+{
+ private long channelId = -1;
+
+ public MuxedFrame()
+ {
+ super();
+ }
+
+ public MuxedFrame(MuxedFrame frame)
+ {
+ super(frame);
+ this.channelId = frame.channelId;
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public void reset()
+ {
+ super.reset();
+ this.channelId = -1;
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder b = new StringBuilder();
+ b.append(OpCode.name(getOpCode()));
+ b.append('[');
+ b.append("channel=").append(channelId);
+ b.append(",len=").append(getPayloadLength());
+ b.append(",fin=").append(isFin());
+ b.append(",rsv=");
+ b.append(isRsv1()?'1':'.');
+ b.append(isRsv2()?'1':'.');
+ b.append(isRsv3()?'1':'.');
+ b.append(",continuation=").append(isContinuation());
+ b.append(']');
+ return b.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/Muxer.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/Muxer.java
new file mode 100644
index 00000000000..71b351ace54
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/Muxer.java
@@ -0,0 +1,425 @@
+//
+// ========================================================================
+// 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.mux;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+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.api.StatusCode;
+import org.eclipse.jetty.websocket.core.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
+import org.eclipse.jetty.websocket.core.api.WebSocketException;
+import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.core.extensions.mux.add.MuxAddClient;
+import org.eclipse.jetty.websocket.core.extensions.mux.add.MuxAddServer;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxNewChannelSlot;
+import org.eclipse.jetty.websocket.core.io.IncomingFrames;
+import org.eclipse.jetty.websocket.core.io.OutgoingFrames;
+import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
+
+/**
+ * Muxer responsible for managing sub-channels.
+ *
+ * Maintains a 1 (incoming and outgoing mux encapsulated frames) to many (per-channel incoming/outgoing standard websocket frames) relationship, along with
+ * routing of {@link MuxControlBlock} events.
+ *
+ * Control Channel events (channel ID == 0) are handled by the Muxer.
+ */
+public class Muxer implements IncomingFrames, MuxParser.Listener
+{
+ private static final int CONTROL_CHANNEL_ID = 0;
+
+ private static final Logger LOG = Log.getLogger(Muxer.class);
+
+ /**
+ * Map of sub-channels, key is the channel Id.
+ */
+ private Map channels = new HashMap();
+
+ private final WebSocketPolicy policy;
+ private final WebSocketConnection physicalConnection;
+ private InetSocketAddress remoteAddress;
+ /** Parsing frames destined for sub-channels */
+ private MuxParser parser;
+ /** Generating frames destined for physical connection */
+ private MuxGenerator generator;
+ private MuxAddServer addServer;
+ private MuxAddClient addClient;
+ /** The original request headers, used for delta encoded AddChannelRequest blocks */
+ private List physicalRequestHeaders;
+ /** The original response headers, used for delta encoded AddChannelResponse blocks */
+ private List physicalResponseHeaders;
+
+ public Muxer(final WebSocketConnection connection, final OutgoingFrames outgoing)
+ {
+ this.physicalConnection = connection;
+ this.policy = connection.getPolicy().clonePolicy();
+ this.parser = new MuxParser();
+ this.parser.setEvents(this);
+ this.generator = new MuxGenerator();
+ this.generator.setOutgoing(outgoing);
+ }
+
+ public MuxAddClient getAddClient()
+ {
+ return addClient;
+ }
+
+ public MuxAddServer getAddServer()
+ {
+ return addServer;
+ }
+
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
+ }
+
+ /**
+ * Get the remote address of the physical connection.
+ *
+ * @return the remote address of the physical connection
+ */
+ public InetSocketAddress getRemoteAddress()
+ {
+ return this.remoteAddress;
+ }
+
+ /**
+ * Incoming exceptions encountered during parsing of mux encapsulated frames.
+ */
+ @Override
+ public void incoming(WebSocketException e)
+ {
+ // TODO Notify Control Channel 0
+ }
+
+ /**
+ * Incoming mux encapsulated frames.
+ */
+ @Override
+ public void incoming(WebSocketFrame frame)
+ {
+ parser.parse(frame);
+ }
+
+ /**
+ * Is the muxer and the physical connection still open?
+ *
+ * @return true if open
+ */
+ public boolean isOpen()
+ {
+ return physicalConnection.isOpen();
+ }
+
+ public String mergeHeaders(List physicalHeaders, String deltaHeaders)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * Per spec, the physical connection must be failed.
+ *
To _Fail the Physical Connection_, an endpoint MUST send a DropChannel multiplex control block with objective channel ID of 0 and drop
+ * reason code in the range of 2000-2999, and then _Fail the WebSocket Connection_ on the physical connection with status code of 1011.
+ */
+ private void mustFailPhysicalConnection(MuxPhysicalConnectionException muxe)
+ {
+ // TODO: stop muxer from receiving incoming sub-channel traffic.
+
+ MuxDropChannel drop = muxe.getMuxDropChannel();
+ LOG.warn(muxe);
+ try
+ {
+ generator.generate(drop);
+ }
+ catch (IOException ioe)
+ {
+ LOG.warn("Unable to send mux DropChannel",ioe);
+ }
+
+ String reason = "Mux[MUST FAIL]" + drop.getPhrase();
+ reason = StringUtil.truncate(reason,WebSocketFrame.MAX_CONTROL_PAYLOAD);
+ this.physicalConnection.close(StatusCode.SERVER_ERROR,reason);
+
+ // TODO: trigger abnormal close for all sub-channels.
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.CLIENT)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelRequest not allowed per spec");
+ }
+
+ if (request.getRsv() != 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_REQUEST_ENCODING,"RSV Not allowed to be set");
+ }
+
+ if (request.getChannelId() == CONTROL_CHANNEL_ID)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
+ }
+
+ // Pre-allocate channel.
+ long channelId = request.getChannelId();
+ MuxChannel channel = new MuxChannel(channelId,this);
+ this.channels.put(channelId,channel);
+
+ // submit to upgrade handshake process
+ try
+ {
+ String requestHandshake = BufferUtil.toUTF8String(request.getHandshake());
+ if (request.isDeltaEncoded())
+ {
+ // Merge original request headers out of physical connection.
+ requestHandshake = mergeHeaders(physicalRequestHeaders,requestHandshake);
+ }
+ String responseHandshake = addServer.handshake(channel,requestHandshake);
+ if (StringUtil.isNotBlank(responseHandshake))
+ {
+ // Upgrade Success
+ MuxAddChannelResponse response = new MuxAddChannelResponse();
+ response.setChannelId(request.getChannelId());
+ response.setFailed(false);
+ response.setHandshake(responseHandshake);
+ // send response
+ this.generator.generate(response);
+ }
+ else
+ {
+ // TODO: trigger error?
+ }
+ }
+ catch (Throwable t)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_REQUEST,"Unable to parse request",t);
+ }
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.SERVER)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"AddChannelResponse not allowed per spec");
+ }
+
+ if (response.getRsv() != 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_RESPONSE_ENCODING,"RSV Not allowed to be set");
+ }
+
+ if (response.getChannelId() == CONTROL_CHANNEL_ID)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
+ }
+
+ // Process channel
+ long channelId = response.getChannelId();
+ MuxChannel channel = this.channels.get(channelId);
+ if (channel == null)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Unknown Channel ID");
+ }
+
+ // Process Response headers
+ try
+ {
+ // Parse Response
+
+ // TODO: Sec-WebSocket-Accept header
+ // TODO: Sec-WebSocket-Extensions header
+ // TODO: Setup extensions
+ // TODO: Setup sessions
+
+ // Trigger channel open
+ channel.onOpen();
+ }
+ catch (Throwable t)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.BAD_RESPONSE,"Unable to parse response",t);
+ }
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxDropChannel(MuxDropChannel drop)
+ {
+ if (drop.getChannelId() == CONTROL_CHANNEL_ID)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
+ }
+
+ // Process channel
+ long channelId = drop.getChannelId();
+ MuxChannel channel = this.channels.get(channelId);
+ if (channel == null)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Unknown Channel ID");
+ }
+
+ String reason = "Mux " + drop.toString();
+ reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
+ channel.close(StatusCode.PROTOCOL,reason);
+ // TODO: set channel to inactive?
+ }
+
+ /**
+ * Incoming mux-unwrapped frames, destined for a sub-channel
+ */
+ @Override
+ public void onMuxedFrame(MuxedFrame frame)
+ {
+ MuxChannel subchannel = channels.get(frame.getChannelId());
+ subchannel.incoming(frame);
+ }
+
+ @Override
+ public void onMuxException(MuxException e)
+ {
+ if (e instanceof MuxPhysicalConnectionException)
+ {
+ mustFailPhysicalConnection((MuxPhysicalConnectionException)e);
+ }
+
+ LOG.warn(e);
+ // TODO: handle other mux exceptions?
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxFlowControl(MuxFlowControl flow)
+ {
+ if (flow.getChannelId() == CONTROL_CHANNEL_ID)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Invalid Channel ID");
+ }
+
+ if (flow.getSendQuotaSize() > 0x7F_FF_FF_FF_FF_FF_FF_FFL)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.SEND_QUOTA_OVERFLOW,"Send Quota Overflow");
+ }
+
+ // Process channel
+ long channelId = flow.getChannelId();
+ MuxChannel channel = this.channels.get(channelId);
+ if (channel == null)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Unknown Channel ID");
+ }
+
+ // TODO: set channel quota
+ }
+
+ /**
+ * Incoming mux control block, destined for the control channel (id 0)
+ */
+ @Override
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
+ {
+ if (policy.getBehavior() == WebSocketBehavior.SERVER)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"NewChannelSlot not allowed per spec");
+ }
+
+ if (slot.isFallback())
+ {
+ if (slot.getNumberOfSlots() == 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 number of slots during fallback");
+ }
+ if (slot.getInitialSendQuota() == 0)
+ {
+ throw new MuxPhysicalConnectionException(MuxDropChannel.Reason.UNKNOWN_MUX_CONTROL_BLOCK,"Cannot have 0 initial send quota during fallback");
+ }
+ }
+
+ // TODO: handle channel slot
+ }
+
+ /**
+ * Outgoing frame, without mux encapsulated payload.
+ */
+ public void output(C context, Callback callback, long channelId, WebSocketFrame frame) throws IOException
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("output({}, {}, {}, {})",context,callback,channelId,frame);
+ }
+ generator.output(context,callback,channelId,frame);
+ }
+
+ public void setAddClient(MuxAddClient addClient)
+ {
+ this.addClient = addClient;
+ }
+
+ public void setAddServer(MuxAddServer addServer)
+ {
+ this.addServer = addServer;
+ }
+
+ /**
+ * Set the remote address of the physical connection.
+ *
+ * This address made available to sub-channels.
+ *
+ * @param remoteAddress
+ * the remote address
+ */
+ public void setRemoteAddress(InetSocketAddress remoteAddress)
+ {
+ this.remoteAddress = remoteAddress;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("Muxer[subChannels.size=%d]", channels.size());
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddClient.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddClient.java
new file mode 100644
index 00000000000..035c76306db
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddClient.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// 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.mux.add;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.core.io.WebSocketSession;
+
+/**
+ * Interface for Mux Client to handle receiving a AddChannelResponse
+ */
+public interface MuxAddClient
+{
+ WebSocketSession createSession(MuxAddChannelResponse response);
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddServer.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddServer.java
new file mode 100644
index 00000000000..06a31135f81
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxAddServer.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// 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.mux.add;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxChannel;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxException;
+import org.eclipse.jetty.websocket.core.io.WebSocketSession;
+
+/**
+ * Server interface, for dealing with incoming AddChannelRequest / AddChannelResponse flows.
+ */
+public interface MuxAddServer
+{
+ /**
+ * Perform the handshake.
+ *
+ * @param channel
+ * the channel to attach the {@link WebSocketSession} to.
+ * @param requestHandshake
+ * the request handshake (request headers)
+ * @return the response handshake (the response headers)
+ * @throws MuxException
+ * if unable to handshake
+ * @throws IOException
+ * if unable to parse request headers
+ */
+ String handshake(MuxChannel channel, String requestHandshake) throws MuxException, IOException;
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelRequest.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelRequest.java
new file mode 100644
index 00000000000..06374224df7
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelRequest.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// 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.mux.op;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp;
+
+public class MuxAddChannelRequest implements MuxControlBlock
+{
+ private long channelId = -1;
+ private byte enc;
+ private ByteBuffer handshake;
+ private byte rsv;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public byte getEnc()
+ {
+ return enc;
+ }
+
+ public ByteBuffer getHandshake()
+ {
+ return handshake;
+ }
+
+ public long getHandshakeSize()
+ {
+ if (handshake == null)
+ {
+ return 0;
+ }
+ return handshake.remaining();
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.ADD_CHANNEL_REQUEST;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isDeltaEncoded()
+ {
+ return (enc == 1);
+ }
+
+ public boolean isIdentityEncoded()
+ {
+ return (enc == 0);
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setEnc(byte enc)
+ {
+ this.enc = enc;
+ }
+
+ public void setHandshake(ByteBuffer handshake)
+ {
+ if (handshake == null)
+ {
+ this.handshake = null;
+ }
+ else
+ {
+ this.handshake = handshake.slice();
+ }
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelResponse.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelResponse.java
new file mode 100644
index 00000000000..ca8e70f521d
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxAddChannelResponse.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// 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.mux.op;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp;
+
+public class MuxAddChannelResponse implements MuxControlBlock
+{
+ private long channelId;
+ private byte enc;
+ private byte rsv;
+ private boolean failed = false;
+ private ByteBuffer handshake;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public byte getEnc()
+ {
+ return enc;
+ }
+
+ public ByteBuffer getHandshake()
+ {
+ return handshake;
+ }
+
+ public long getHandshakeSize()
+ {
+ if (handshake == null)
+ {
+ return 0;
+ }
+ return handshake.remaining();
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.ADD_CHANNEL_RESPONSE;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isFailed()
+ {
+ return failed;
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setEnc(byte enc)
+ {
+ this.enc = enc;
+ }
+
+ public void setFailed(boolean failed)
+ {
+ this.failed = failed;
+ }
+
+ public void setHandshake(ByteBuffer handshake)
+ {
+ if (handshake == null)
+ {
+ this.handshake = null;
+ }
+ else
+ {
+ this.handshake = handshake.slice();
+ }
+ }
+
+ public void setHandshake(String responseHandshake)
+ {
+ setHandshake(BufferUtil.toBuffer(responseHandshake));
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxDropChannel.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxDropChannel.java
new file mode 100644
index 00000000000..a93b13296f2
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxDropChannel.java
@@ -0,0 +1,183 @@
+//
+// ========================================================================
+// 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.mux.op;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp;
+
+public class MuxDropChannel implements MuxControlBlock
+{
+ /**
+ * Outlined in Section 9.4.1. Drop Reason Codes
+ */
+ public static enum Reason
+ {
+ // Normal Close : (1000-1999)
+ NORMAL_CLOSURE(1000),
+
+ // Failures in Physical Connection : (2000-2999)
+ PHYSICAL_CONNECTION_FAILED(2000),
+ INVALID_ENCAPSULATING_MESSAGE(2001),
+ CHANNEL_ID_TRUNCATED(2002),
+ ENCAPSULATED_FRAME_TRUNCATED(2003),
+ UNKNOWN_MUX_CONTROL_OPC(2004),
+ UNKNOWN_MUX_CONTROL_BLOCK(2005),
+ CHANNEL_ALREADY_EXISTS(2006),
+ NEW_CHANNEL_SLOT_VIOLATION(2007),
+ NEW_CHANNEL_SLOT_OVERFLOW(2008),
+ BAD_REQUEST(2009),
+ UNKNOWN_REQUEST_ENCODING(2010),
+ BAD_RESPONSE(2011),
+ UNKNOWN_RESPONSE_ENCODING(2012),
+
+ // Failures in Logical Connections : (3000-3999)
+ LOGICAL_CHANNEL_FAILED(3000),
+ SEND_QUOTA_VIOLATION(3005),
+ SEND_QUOTA_OVERFLOW(3006),
+ IDLE_TIMEOUT(3007),
+ DROP_CHANNEL_ACK(3008),
+
+ // Other Peer Actions : (4000-4999)
+ USE_ANOTHER_PHYSICAL_CONNECTION(4001),
+ BUSY(4002);
+
+ private static final Map codeMap;
+
+ static
+ {
+ codeMap = new HashMap<>();
+ for (Reason r : values())
+ {
+ codeMap.put(r.getValue(),r);
+ }
+ }
+
+ public static Reason valueOf(int code)
+ {
+ return codeMap.get(code);
+ }
+
+ private final int code;
+
+ private Reason(int code)
+ {
+ this.code = code;
+ }
+
+ public int getValue()
+ {
+ return code;
+ }
+ }
+
+ public static MuxDropChannel parse(long channelId, ByteBuffer payload)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private final long channelId;
+ private final Reason code;
+ private String phrase;
+ private int rsv;
+
+ /**
+ * Normal Drop. no reason Phrase.
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ */
+ public MuxDropChannel(long channelId)
+ {
+ this(channelId,Reason.NORMAL_CLOSURE,null);
+ }
+
+ /**
+ * Drop with reason code and optional phrase
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ * @param code
+ * reason code
+ * @param phrase
+ * optional human readable phrase
+ */
+ public MuxDropChannel(long channelId, int code, String phrase)
+ {
+ this(channelId, Reason.valueOf(code), phrase);
+ }
+
+ /**
+ * Drop with reason code and optional phrase
+ *
+ * @param channelId
+ * the logical channel Id to perform drop against.
+ * @param code
+ * reason code
+ * @param phrase
+ * optional human readable phrase
+ */
+ public MuxDropChannel(long channelId, Reason code, String phrase)
+ {
+ this.channelId = channelId;
+ this.code = code;
+ this.phrase = phrase;
+ }
+
+ public ByteBuffer asReasonBuffer()
+ {
+ // TODO: convert to reason buffer
+ return null;
+ }
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ public Reason getCode()
+ {
+ return code;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.DROP_CHANNEL;
+ }
+
+ public String getPhrase()
+ {
+ return phrase;
+ }
+
+ public int getRsv()
+ {
+ return rsv;
+ }
+
+ public void setRsv(int rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxFlowControl.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxFlowControl.java
new file mode 100644
index 00000000000..afb8454f70f
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxFlowControl.java
@@ -0,0 +1,65 @@
+//
+// ========================================================================
+// 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.mux.op;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp;
+
+public class MuxFlowControl implements MuxControlBlock
+{
+ private long channelId;
+ private byte rsv;
+ private long sendQuotaSize;
+
+ public long getChannelId()
+ {
+ return channelId;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.FLOW_CONTROL;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public long getSendQuotaSize()
+ {
+ return sendQuotaSize;
+ }
+
+ public void setChannelId(long channelId)
+ {
+ this.channelId = channelId;
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+
+ public void setSendQuotaSize(long sendQuotaSize)
+ {
+ this.sendQuotaSize = sendQuotaSize;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxNewChannelSlot.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxNewChannelSlot.java
new file mode 100644
index 00000000000..903beb9a545
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/extensions/mux/op/MuxNewChannelSlot.java
@@ -0,0 +1,76 @@
+//
+// ========================================================================
+// 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.mux.op;
+
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxControlBlock;
+import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp;
+
+public class MuxNewChannelSlot implements MuxControlBlock
+{
+ private boolean fallback;
+ private long initialSendQuota;
+ private long numberOfSlots;
+ private byte rsv;
+
+ public long getInitialSendQuota()
+ {
+ return initialSendQuota;
+ }
+
+ public long getNumberOfSlots()
+ {
+ return numberOfSlots;
+ }
+
+ @Override
+ public int getOpCode()
+ {
+ return MuxOp.NEW_CHANNEL_SLOT;
+ }
+
+ public byte getRsv()
+ {
+ return rsv;
+ }
+
+ public boolean isFallback()
+ {
+ return fallback;
+ }
+
+ public void setFallback(boolean fallback)
+ {
+ this.fallback = fallback;
+ }
+
+ public void setInitialSendQuota(long initialSendQuota)
+ {
+ this.initialSendQuota = initialSendQuota;
+ }
+
+ public void setNumberOfSlots(long numberOfSlots)
+ {
+ this.numberOfSlots = numberOfSlots;
+ }
+
+ public void setRsv(byte rsv)
+ {
+ this.rsv = rsv;
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/event/EventDriver.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/event/EventDriver.java
index d02010dd931..00fb6694ea2 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/event/EventDriver.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/io/event/EventDriver.java
@@ -180,14 +180,7 @@ public abstract class EventDriver implements IncomingFrames
protected void terminateConnection(int statusCode, String rawreason)
{
String reason = rawreason;
- if (StringUtil.isNotBlank(reason))
- {
- // Trim big exception messages here.
- if (reason.length() > (WebSocketFrame.MAX_CONTROL_PAYLOAD - 2))
- {
- reason = reason.substring(0,WebSocketFrame.MAX_CONTROL_PAYLOAD - 2);
- }
- }
+ reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
LOG.debug("terminateConnection({},{})",statusCode,rawreason);
session.close(statusCode,reason);
}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/WebSocketFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/WebSocketFrame.java
index 1b00f826a23..e1f0051a1d1 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/WebSocketFrame.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/protocol/WebSocketFrame.java
@@ -556,6 +556,10 @@ public class WebSocketFrame implements Frame
b.append('[');
b.append("len=").append(payloadLength);
b.append(",fin=").append(fin);
+ b.append(",rsv=");
+ b.append(rsv1?'1':'.');
+ b.append(rsv2?'1':'.');
+ b.append(rsv3?'1':'.');
b.append(",masked=").append(masked);
b.append(",continuation=").append(continuation);
b.append(']');
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/examples/echo/AdapterEchoSocket.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/examples/echo/AdapterEchoSocket.java
index e6ac3699868..8981f653e0c 100644
--- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/examples/echo/AdapterEchoSocket.java
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/examples/echo/AdapterEchoSocket.java
@@ -20,6 +20,8 @@ package org.eclipse.jetty.websocket.core.examples.echo;
import java.io.IOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.api.WebSocketAdapter;
/**
@@ -27,16 +29,20 @@ import org.eclipse.jetty.websocket.core.api.WebSocketAdapter;
*/
public class AdapterEchoSocket extends WebSocketAdapter
{
+ private static final Logger LOG = Log.getLogger(AdapterEchoSocket.class);
+
@Override
public void onWebSocketText(String message)
{
if (isNotConnected())
{
+ LOG.debug("WebSocket Not Connected");
return;
}
try
{
+ LOG.debug("Echoing back message [{}]",message);
// echo the data back
getBlockingConnection().write(message);
}
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxEventCapture.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxEventCapture.java
new file mode 100644
index 00000000000..6c3cf12a71d
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxEventCapture.java
@@ -0,0 +1,137 @@
+//
+// ========================================================================
+// 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.mux;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelRequest;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxAddChannelResponse;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxDropChannel;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxFlowControl;
+import org.eclipse.jetty.websocket.core.extensions.mux.op.MuxNewChannelSlot;
+import org.eclipse.jetty.websocket.core.protocol.OpCode;
+import org.junit.Assert;
+
+public class MuxEventCapture implements MuxParser.Listener
+{
+ private static final Logger LOG = Log.getLogger(MuxEventCapture.class);
+
+ private LinkedList frames = new LinkedList<>();
+ private LinkedList ops = new LinkedList<>();
+ private LinkedList errors = new LinkedList<>();
+
+ public void assertFrameCount(int expected)
+ {
+ Assert.assertThat("Frame Count",frames.size(), is(expected));
+ }
+
+ public void assertHasFrame(byte opcode, long channelId, int expectedCount)
+ {
+ int actualCount = 0;
+
+ for (MuxedFrame frame : frames)
+ {
+ if (frame.getChannelId() == channelId)
+ {
+ if (frame.getOpCode() == opcode)
+ {
+ actualCount++;
+ }
+ }
+ }
+
+ Assert.assertThat("Expected Count of " + OpCode.name(opcode) + " frames on Channel ID " + channelId,actualCount,is(expectedCount));
+ }
+
+ public void assertHasOp(byte opCode, int expectedCount)
+ {
+ int actualCount = 0;
+ for (MuxControlBlock block : ops)
+ {
+ if (block.getOpCode() == opCode)
+ {
+ actualCount++;
+ }
+ }
+ Assert.assertThat("Op[" + opCode + "] count",actualCount,is(expectedCount));
+ }
+
+ public LinkedList getFrames()
+ {
+ return frames;
+ }
+
+ public LinkedList getOps()
+ {
+ return ops;
+ }
+
+ @Override
+ public void onMuxAddChannelRequest(MuxAddChannelRequest request)
+ {
+ ops.add(request);
+ }
+
+ @Override
+ public void onMuxAddChannelResponse(MuxAddChannelResponse response)
+ {
+ ops.add(response);
+ }
+
+ @Override
+ public void onMuxDropChannel(MuxDropChannel drop)
+ {
+ ops.add(drop);
+ }
+
+ @Override
+ public void onMuxedFrame(MuxedFrame frame)
+ {
+ frames.add(new MuxedFrame(frame));
+ }
+
+ @Override
+ public void onMuxException(MuxException e)
+ {
+ LOG.debug(e);
+ errors.add(e);
+ }
+
+ @Override
+ public void onMuxFlowControl(MuxFlowControl flow)
+ {
+ ops.add(flow);
+ }
+
+ @Override
+ public void onMuxNewChannelSlot(MuxNewChannelSlot slot)
+ {
+ ops.add(slot);
+ }
+
+ public void reset()
+ {
+ frames.clear();
+ ops.clear();
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWrite139SizeTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWrite139SizeTest.java
new file mode 100644
index 00000000000..a14abe23d7c
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWrite139SizeTest.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// 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.mux;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MuxGeneratorWrite139SizeTest
+{
+ private static MuxGenerator generator = new MuxGenerator();
+
+ @Parameters
+ public static Collection
- This demo uses a simple login module that stores its configuration in a properties file. There are other types of login module provided with the jetty distro. For full information, please refer to the jetty documentation: http://www.eclipse.org/jetty/documentation/current/.
+ This demo uses a simple login module that stores its configuration in a properties file. There are other types of login module provided with the jetty distro. For full information, please refer to the Jetty 9 documentation.
To authenticate successfully with this demonstration, you must use username="me" with password="me". All other usernames, passwords should result in authentication failure.
diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml
new file mode 100644
index 00000000000..49cfa087650
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/pom.xml
@@ -0,0 +1,153 @@
+
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-webapps-parent
+ 9.0.0-SNAPSHOT
+
+ test-jndi-webapp
+ Jetty Tests :: WebApp :: JNDI
+ war
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+
+ true
+
+
+
+ maven-antrun-plugin
+
+
+ generate-xml-files
+ process-resources
+
+
+
+
+
+
+
+
+
+
+
+ run
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ package
+
+ copy
+
+
+
+
+ org.eclipse.jetty.tests
+ test-mock-resources
+ ${project.version}
+ jar
+ **
+ true
+ ${project.build.directory}/lib/jndi
+
+
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-transaction-properties
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/resources
+
+
+ src/main/config/resources
+
+ **/transactions.properties
+
+ true
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.2-beta-3
+
+
+ package
+
+ single
+
+
+
+ src/main/assembly/config.xml
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+ ${project.version}
+
+ ${project.build.directory}/plugin-context.xml
+
+ src/main/webapp
+ src/main/webapp/WEB-INF/web.xml
+ /test-jndi
+
+
+
+
+
+
+
+ org.eclipse.jetty.orbit
+ javax.transaction
+ 1.1.1.v201105210645
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.servlet
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.mail.glassfish
+ provided
+ 1.4.1.v201005082020
+
+
+
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
new file mode 100644
index 00000000000..e307ad4d0a7
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
@@ -0,0 +1,34 @@
+
+
+ config
+ false
+
+ jar
+
+
+
+ src/main/config
+
+
+ **
+
+
+ **/resources/**
+
+
+
+ target
+ webapps
+
+ test-jndi.xml
+
+
+
+ target
+
+
+ lib/jndi/**
+
+
+
+
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
new file mode 100644
index 00000000000..021a21152c6
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
@@ -0,0 +1,161 @@
+//
+// ========================================================================
+// 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 com.acme;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import javax.mail.Session;
+import javax.naming.InitialContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.sql.DataSource;
+import javax.transaction.UserTransaction;
+
+/**
+ * JNDITest
+ *
+ * Use JNDI from within Jetty.
+ *
+ * Also, use servlet spec 2.5 resource injection and lifecycle callbacks from within the web.xml
+ * to set up some of the JNDI resources.
+ *
+ */
+public class JNDITest extends HttpServlet
+{
+ private DataSource myDS;
+
+ private Session myMailSession;
+ private Double wiggle;
+ private Integer woggle;
+ private Double gargle;
+
+ private String resourceNameMappingInjectionResult;
+ private String envEntryOverrideResult;
+ private String postConstructResult = "PostConstruct method called: FALSE";
+ private String preDestroyResult = "PreDestroy method called: NOT YET";
+ private String envEntryGlobalScopeResult;
+ private String envEntryWebAppScopeResult;
+ private String userTransactionResult;
+ private String mailSessionResult;
+
+
+ public void setMyDatasource(DataSource ds)
+ {
+ myDS=ds;
+ }
+
+
+ private void postConstruct ()
+ {
+ String tmp = (myDS == null?"":myDS.toString());
+ resourceNameMappingInjectionResult= "Injection of resource to locally mapped name (java:comp/env/mydatasource as java:comp/env/mydatasource1): "+String.valueOf(myDS);
+ envEntryOverrideResult = "Override of EnvEntry in jetty-env.xml (java:comp/env/wiggle): "+(wiggle==55.0?"PASS":"FAIL(expected 55.0, got "+wiggle+")");
+ postConstructResult = "PostConstruct method called: PASS";
+ }
+
+ private void preDestroy()
+ {
+ preDestroyResult = "PreDestroy method called: PASS";
+ }
+
+
+ public void init(ServletConfig config) throws ServletException
+ {
+ super.init(config);
+ try
+ {
+ InitialContext ic = new InitialContext();
+ woggle = (Integer)ic.lookup("java:comp/env/woggle");
+ envEntryGlobalScopeResult = "EnvEntry defined in context xml lookup result (java:comp/env/woggle): "+(woggle==4000?"PASS":"FAIL(expected 4000, got "+woggle+")");
+ gargle = (Double)ic.lookup("java:comp/env/gargle");
+ envEntryWebAppScopeResult = "EnvEntry defined in jetty-env.xml lookup result (java:comp/env/gargle): "+(gargle==100.0?"PASS":"FAIL(expected 100, got "+gargle+")");
+ UserTransaction utx = (UserTransaction)ic.lookup("java:comp/UserTransaction");
+ userTransactionResult = "UserTransaction lookup result (java:comp/UserTransaction): "+(utx!=null?"PASS":"FAIL");
+ myMailSession = (Session)ic.lookup("java:comp/env/mail/Session");
+ mailSessionResult = "Mail Session lookup result (java:comp/env/mail/Session): "+(myMailSession!=null?"PASS": "FAIL");
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ doGet(request, response);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ String mailTo = request.getParameter("mailto");
+ String mailFrom = request.getParameter("mailfrom");
+
+ if (mailTo != null)
+ mailTo = mailTo.trim();
+
+ if (mailFrom != null)
+ mailFrom = mailFrom.trim();
+
+ try
+ {
+ response.setContentType("text/html");
+ ServletOutputStream out = response.getOutputStream();
+ out.println("");
+ out.println("
+
+ Test Web Application Only - Do NOT Deploy in Production
+
+
+
+
JNDI Test WebApp
+
+
+This example shows how to configure and lookup resources such as DataSources, a JTA transaction manager and a java.mail.Session in JNDI.
+
+
+
Preparation
+
+
+
Ensure that you have downloaded and unpacked the test-jndi-webapp-<version>-config.jar, where <version> is replaced by the desired version number. The following files will be created:
+
+ lib/jndi/test-mock-resources-<version>.jar (where <version> is replaced by the particular version number)
+ webapps/test-jndi.xml
+
+
+
Edit your $JETTY_HOME/start.ini file and add the following lines:
+
+ OPTIONS=plus
+ OPTIONS=jta
+
+
+
+
+
+
Execution
+
+Click Test to check the runtime lookup of the JNDI resources.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml
new file mode 100644
index 00000000000..0ee6b5b7673
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/pom.xml
@@ -0,0 +1,35 @@
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-webapps-parent
+ 9.0.0-SNAPSHOT
+
+ Jetty Tests :: WebApp :: Mock Resources
+ test-mock-resources
+ jar
+
+
+
+ maven-compiler-plugin
+
+
+ 1.6
+ false
+
+
+
+
+
+
+ org.eclipse.jetty.orbit
+ javax.transaction
+ 1.1.1.v201105210645
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.servlet
+
+
+
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java
new file mode 100644
index 00000000000..4fbdf8b71c4
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// 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 com.acme;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+/**
+ * MockDataSource
+ *
+ *
+ */
+public class MockDataSource implements DataSource
+{
+
+ /**
+ * NOTE: JDK7+ new feature
+ */
+ public Logger getParentLogger()
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.sql.DataSource#getConnection()
+ */
+ public Connection getConnection() throws SQLException
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
+ */
+ public Connection getConnection(String username, String password)
+ throws SQLException
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.sql.DataSource#getLogWriter()
+ */
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.sql.DataSource#getLoginTimeout()
+ */
+ public int getLoginTimeout() throws SQLException
+ {
+ return 0;
+ }
+
+ /**
+ * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
+ */
+ public void setLogWriter(PrintWriter out) throws SQLException
+ {
+ }
+
+ /**
+ * @see javax.sql.DataSource#setLoginTimeout(int)
+ */
+ public void setLoginTimeout(int seconds) throws SQLException
+ {
+ }
+
+ public boolean isWrapperFor(Class> iface) throws SQLException
+ {
+ return false;
+ }
+
+ public T unwrap(Class iface) throws SQLException
+ {
+ return null;
+ }
+
+}
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java
new file mode 100644
index 00000000000..cabf3a103ce
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// 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 com.acme;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+/**
+ * MockUserTransaction
+ *
+ *
+ */
+public class MockUserTransaction implements UserTransaction
+{
+
+ /**
+ * @see javax.transaction.UserTransaction#begin()
+ */
+ public void begin() throws NotSupportedException, SystemException
+ {
+ }
+
+ /**
+ * @see javax.transaction.UserTransaction#commit()
+ */
+ public void commit() throws HeuristicMixedException,
+ HeuristicRollbackException, IllegalStateException,
+ RollbackException, SecurityException, SystemException
+ {
+ }
+
+ /**
+ * @see javax.transaction.UserTransaction#getStatus()
+ */
+ public int getStatus() throws SystemException
+ {
+ return 0;
+ }
+
+ /**
+ * @see javax.transaction.UserTransaction#rollback()
+ */
+ public void rollback() throws IllegalStateException, SecurityException,
+ SystemException
+ {
+ }
+
+ /**
+ * @see javax.transaction.UserTransaction#setRollbackOnly()
+ */
+ public void setRollbackOnly() throws IllegalStateException, SystemException
+ {
+ }
+
+ /**
+ * @see javax.transaction.UserTransaction#setTransactionTimeout(int)
+ */
+ public void setTransactionTimeout(int arg0) throws SystemException
+ {
+ }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml
new file mode 100644
index 00000000000..9e835334689
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-webapps-parent
+ 9.0.0-SNAPSHOT
+
+ test-servlet-spec-parent
+ Jetty Tests :: Spec Test WebApp :: Parent
+ pom
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+
+ true
+
+
+
+
+
+ test-web-fragment
+ test-container-initializer
+ test-spec-webapp
+
+
+
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
new file mode 100644
index 00000000000..fe9bf936443
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
@@ -0,0 +1,30 @@
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-servlet-spec-parent
+ 9.0.0-SNAPSHOT
+
+ test-container-initializer
+ jar
+ Jetty Tests :: WebApp :: Servlet Spec :: ServletContainerInitializer Test Jar
+
+
+
+ maven-compiler-plugin
+
+
+ 1.6
+ false
+
+
+
+
+
+
+ org.eclipse.jetty.orbit
+ javax.servlet
+ provided
+
+
+
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/Foo.java b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/Foo.java
new file mode 100644
index 00000000000..c9b4d89e8d7
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/Foo.java
@@ -0,0 +1,15 @@
+package com.acme;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+public @interface Foo
+{
+ int value();
+}
+
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/FooInitializer.java b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/FooInitializer.java
new file mode 100644
index 00000000000..f80409ebf0d
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/FooInitializer.java
@@ -0,0 +1,20 @@
+package com.acme;
+
+import java.util.Set;
+import java.util.ArrayList;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.ServletContainerInitializer;
+
+@HandlesTypes ({javax.servlet.Servlet.class, Foo.class})
+public class FooInitializer implements ServletContainerInitializer
+{
+
+ public void onStartup(Set> classes, ServletContext context)
+ {
+ context.setAttribute("com.acme.Foo", new ArrayList(classes));
+ ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "com.acme.AnnotationTest");
+ context.setAttribute("com.acme.AnnotationTest.complete", (reg == null));
+ }
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 00000000000..264910bf918
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+com.acme.FooInitializer
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
new file mode 100644
index 00000000000..2ee913bfb87
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
@@ -0,0 +1,165 @@
+
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-servlet-spec-parent
+ 9.0.0-SNAPSHOT
+
+ Jetty Tests :: Webapps :: Spec Webapp
+ test-spec-webapp
+ war
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ maven-antrun-plugin
+
+
+ generate-xml-files
+ process-resources
+
+
+
+
+
+
+
+
+
+
+
+ run
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ package
+
+ copy
+
+
+
+
+ org.eclipse.jetty.tests
+ test-mock-resources
+ ${project.version}
+ jar
+ **
+ true
+ ${project.build.directory}/lib/jndi
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.2-beta-3
+
+
+ package
+
+ single
+
+
+
+ ${basedir}/src/main/assembly/config.xml
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+ ${project.version}
+
+ 10
+ true
+ ${project.build.directory}/plugin-context.xml
+
+ src/main/webapp
+ src/main/webapp/WEB-INF/web.xml
+ /test-annotations
+ .*/javax.servlet-[^/]*\.jar$
+ true
+ ${basedir}/src/main/webapp/WEB-INF/jetty-env.xml
+
+
+
+ Test Realm
+ src/etc/realm.properties
+
+
+
+
+
+ org.eclipse.jetty.tests
+ test-mock-resources
+ ${project.version}
+
+
+
+
+
+
+
+ org.eclipse.jetty.orbit
+ javax.transaction
+ 1.1.1.v201105210645
+ provided
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${project.version}
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.mail.glassfish
+ 1.4.1.v201005082020
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.servlet
+ provided
+
+
+ org.eclipse.jetty.orbit
+ javax.annotation
+ 1.1.0.v201108011116
+ provided
+
+
+ org.eclipse.jetty.tests
+ test-web-fragment
+ ${project.version}
+
+
+ org.eclipse.jetty.tests
+ test-container-initializer
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+
+
+
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties
new file mode 100644
index 00000000000..ca4cfc5be5e
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties
@@ -0,0 +1,8 @@
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
new file mode 100644
index 00000000000..d1c38d25ac2
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
@@ -0,0 +1,34 @@
+
+
+ config
+ false
+
+ jar
+
+
+
+ src/main/config
+
+
+ **
+
+
+ **/resources/**
+
+
+
+ target
+ webapps
+
+ test-spec.xml
+
+
+
+ target
+
+
+ lib/jndi/**
+
+
+
+
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
new file mode 100644
index 00000000000..587f551378e
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
@@ -0,0 +1,319 @@
+//========================================================================
+//Copyright 2004-2009 Mort Bay Consulting Pty. Ltd.
+//------------------------------------------------------------------------
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//http://www.apache.org/licenses/LICENSE-2.0
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//========================================================================
+
+/**
+ *
+ */
+package com.acme;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.naming.InitialContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.sql.DataSource;
+import javax.transaction.UserTransaction;
+import javax.annotation.Resource;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.security.RunAs;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.WebInitParam;
+import javax.annotation.security.DeclareRoles;
+
+/**
+ * AnnotationTest
+ *
+ * Use Annotations from within Jetty.
+ *
+ * Also, use servlet spec 2.5 resource injection and lifecycle callbacks from within the web.xml
+ * to set up some of the JNDI resources.
+ *
+ */
+
+@RunAs("special")
+@WebServlet(urlPatterns = {"/","/test/*"}, name="AnnotationTest", initParams={@WebInitParam(name="fromAnnotation", value="xyz")})
+@DeclareRoles({"user","client"})
+public class AnnotationTest extends HttpServlet
+{
+ static List __HandlesTypes;
+ private String postConstructResult = "";
+ private String dsResult = "";
+ private String envResult = "";
+ private String envLookupResult = "";
+ private String envResult2 ="";
+ private String envLookupResult2 = "";
+ private String envResult3 = "";
+ private String envLookupResult3 = "";
+ private String dsLookupResult = "";
+ private String txResult = "";
+ private String txLookupResult = "";
+ private DataSource myDS;
+ private ServletConfig config;
+
+ @Resource(mappedName="UserTransaction")
+ private UserTransaction myUserTransaction;
+
+
+ @Resource(mappedName="maxAmount")
+ private Double maxAmount;
+
+ @Resource(name="someAmount")
+ private Double minAmount;
+
+ @Resource
+ private Double avgAmount;
+
+
+ @Resource(mappedName="jdbc/mydatasource")
+ public void setMyDatasource(DataSource ds)
+ {
+ myDS=ds;
+ }
+
+
+ @PostConstruct
+ private void myPostConstructMethod ()
+ {
+ postConstructResult = "Called";
+ try
+ {
+ dsResult = (myDS==null?"FAIL":"myDS="+myDS.toString());
+ }
+ catch (Exception e)
+ {
+ dsResult = "FAIL: "+e;
+ }
+
+
+ envResult = (maxAmount==null?"FAIL":"maxAmount="+maxAmount.toString());
+
+ try
+ {
+ InitialContext ic = new InitialContext();
+ envLookupResult = "java:comp/env/com.acme.AnnotationTest/maxAmount="+ic.lookup("java:comp/env/com.acme.AnnotationTest/maxAmount");
+ }
+ catch (Exception e)
+ {
+ envLookupResult = "FAIL: "+e;
+ }
+
+ envResult2 = (minAmount==null?"FAIL":"minAmount="+minAmount.toString());
+ try
+ {
+ InitialContext ic = new InitialContext();
+ envLookupResult2 = "java:comp/env/someAmount="+ic.lookup("java:comp/env/someAmount");
+ }
+ catch (Exception e)
+ {
+ envLookupResult2 = "FAIL: "+e;
+ }
+ envResult3 = (minAmount==null?"FAIL":"avgAmount="+avgAmount.toString());
+ try
+ {
+ InitialContext ic = new InitialContext();
+ envLookupResult3 = "java:comp/env/com.acme.AnnotationTest/avgAmount="+ic.lookup("java:comp/env/com.acme.AnnotationTest/avgAmount");
+ }
+ catch (Exception e)
+ {
+ envLookupResult3 = "FAIL: "+e;
+ }
+
+
+
+ try
+ {
+ InitialContext ic = new InitialContext();
+ dsLookupResult = "java:comp/env/com.acme.AnnotationTest/myDatasource="+ic.lookup("java:comp/env/com.acme.AnnotationTest/myDatasource");
+ }
+ catch (Exception e)
+ {
+ dsLookupResult = "FAIL: "+e;
+ }
+
+ txResult = (myUserTransaction==null?"FAIL":"myUserTransaction="+myUserTransaction);
+ try
+ {
+ InitialContext ic = new InitialContext();
+ txLookupResult = "java:comp/env/com.acme.AnnotationTest/myUserTransaction="+ic.lookup("java:comp/env/com.acme.AnnotationTest/myUserTransaction");
+ }
+ catch (Exception e)
+ {
+ txLookupResult = "FAIL: "+e;
+ }
+ }
+
+ @PreDestroy
+ private void myPreDestroyMethod()
+ {
+ }
+
+ public void init(ServletConfig config) throws ServletException
+ {
+ super.init(config);
+ this.config = config;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ doGet(request, response);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ try
+ {
+ response.setContentType("text/html");
+ ServletOutputStream out = response.getOutputStream();
+ out.println("");
+ out.println("");
+ out.println("
+
+ Test Web Application Only - Do NOT Deploy in Production
+
+
+
Advanced Servlet Specification Test WebApp
+
+
+This example uses and tests newer aspects of the servlet specification such as annotations, web-fragments and servlet container initializers.
+
+
+
Preparation
+
+
+
Ensure that you have downloaded and unpacked the test-spec-webapp-<version>-config.jar, where is replaced by the desired version number. The following files will be created:
+
+ lib/jndi/test-mock-resources-<version>.jar (where <version> is replaced by the particular version number)
+ webapps/test-annotations.xml
+
+
+
Edit your $JETTY_HOME/start.ini file and add the following lines:
+