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.xml 1 true + + + + + + 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 entry : properties.entrySet()) _map.put(entry.getKey().toString(),String.valueOf(entry.getValue())); } diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java index 61aa16509d1..8b301d8035d 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.MalformedURLException; +import java.util.Locale; import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.ConfigurationManager; @@ -77,7 +78,7 @@ public class WebAppProvider extends ScanningAppProvider { return false; } - String lowername = name.toLowerCase(); + String lowername = name.toLowerCase(Locale.ENGLISH); File file = new File(dir,name); @@ -299,9 +300,9 @@ public class WebAppProvider extends ScanningAppProvider { context = URIUtil.SLASH; } - else if (context.toLowerCase().startsWith("root-")) + else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-")) { - int dash=context.toLowerCase().indexOf('-'); + int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-'); String virtual = context.substring(dash+1); wah.setVirtualHosts(new String[]{virtual}); context = URIUtil.SLASH; diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java index cda1fe21a88..7df726926f0 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.deploy.util; import java.io.File; +import java.util.Locale; /** * Simple, yet surprisingly common utility methods for identifying various file types commonly seen and worked with in a @@ -38,7 +39,7 @@ public class FileID { if (path.isFile()) { - String name = path.getName().toLowerCase(); + String name = path.getName().toLowerCase(Locale.ENGLISH); return (name.endsWith(".war") || name.endsWith(".jar")); } @@ -62,7 +63,7 @@ public class FileID return false; } - String name = path.getName().toLowerCase(); + String name = path.getName().toLowerCase(Locale.ENGLISH); return (name.endsWith(".war") || name.endsWith(".jar")); } @@ -73,7 +74,7 @@ public class FileID return false; } - String name = path.getName().toLowerCase(); + String name = path.getName().toLowerCase(Locale.ENGLISH); return name.endsWith(".xml"); } } diff --git a/jetty-deploy/src/test/resources/binding-test-contexts-1.xml b/jetty-deploy/src/test/resources/binding-test-contexts-1.xml index 1614056f430..37f5292ff2e 100644 --- a/jetty-deploy/src/test/resources/binding-test-contexts-1.xml +++ b/jetty-deploy/src/test/resources/binding-test-contexts-1.xml @@ -50,7 +50,7 @@ /webapps 1 - + /xml-configured-jetty.properties diff --git a/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml index acf69585ff5..ba38daef80d 100644 --- a/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml +++ b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml @@ -17,7 +17,7 @@ /webapps 1 - + /xml-configured-jetty.properties diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 2d4e4756318..20f8027a298 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -364,6 +364,12 @@ ${project.version} + + org.eclipse.jetty + jetty-jaas + ${project.version} + + org.eclipse.jetty jetty-annotations @@ -381,11 +387,6 @@ jetty-jndi ${project.version} - - org.eclipse.jetty - jetty-plus - ${project.version} - org.eclipse.jetty jetty-client diff --git a/jetty-distribution/src/main/resources/webapps/javadoc.xml b/jetty-distribution/src/main/resources/webapps/javadoc.xml index 3fec5c07feb..71567efd934 100644 --- a/jetty-distribution/src/main/resources/webapps/javadoc.xml +++ b/jetty-distribution/src/main/resources/webapps/javadoc.xml @@ -1,13 +1,7 @@ - - + /javadoc /javadoc/ @@ -21,6 +15,5 @@ to serve static html files and images. max-age=3600,public - diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 6657b94f4a3..94551ed56cd 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -810,7 +810,7 @@ public class HttpFields implements Iterable { hasDomain = true; buf.append(";Domain="); - QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(),delim); + QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim); } if (maxAge >= 0) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index b252ca99025..17b6056a54d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Enumeration; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -70,7 +71,7 @@ public class MimeTypes _string=s; _buffer=BufferUtil.toBuffer(s); - int i=s.toLowerCase().indexOf("charset="); + int i=s.toLowerCase(Locale.ENGLISH).indexOf("charset="); _charset=(i>0)?Charset.forName(s.substring(i+8)):null; } diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties index 1d4c73da6ef..74601e30851 100644 --- a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties +++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties @@ -171,6 +171,7 @@ xhtml=application/xhtml+xml xls=application/vnd.ms-excel xml=application/xml xpm=image/x-xpixmap +xsd=application/xml xsl=application/xml xslt=application/xslt+xml xul=application/vnd.mozilla.xul+xml diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 3eba7713e9f..ea1198a786f 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; import java.util.Enumeration; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import org.eclipse.jetty.util.BufferUtil; @@ -385,7 +386,7 @@ public class HttpFieldsTest { Set s=new HashSet(); while(e.hasMoreElements()) - s.add(e.nextElement().toLowerCase()); + s.add(e.nextElement().toLowerCase(Locale.ENGLISH)); return s; } diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml index 748e8b62751..9128322745f 100644 --- a/jetty-jaas/pom.xml +++ b/jetty-jaas/pom.xml @@ -45,7 +45,6 @@ - diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/jetty-jaas.xml b/jetty-jaas/src/main/config/etc/jetty-jaas.xml similarity index 62% rename from tests/test-webapps/test-jaas-webapp/src/main/config/etc/jetty-jaas.xml rename to jetty-jaas/src/main/config/etc/jetty-jaas.xml index 7b083ffdcd0..0d9613dfbeb 100644 --- a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/jetty-jaas.xml +++ b/jetty-jaas/src/main/config/etc/jetty-jaas.xml @@ -14,17 +14,4 @@ /etc/login.conf - - - - - - - - Test JAAS Realm - xyz - - - - diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java index 734dece175d..649b31bbc7b 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import javax.naming.Context; @@ -652,12 +653,12 @@ public class LdapLoginModule extends AbstractLoginModule public static String convertCredentialJettyToLdap(String encryptedPassword) { - if ("MD5:".startsWith(encryptedPassword.toUpperCase())) + if ("MD5:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH))) { return "{MD5}" + encryptedPassword.substring("MD5:".length(), encryptedPassword.length()); } - if ("CRYPT:".startsWith(encryptedPassword.toUpperCase())) + if ("CRYPT:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH))) { return "{CRYPT}" + encryptedPassword.substring("CRYPT:".length(), encryptedPassword.length()); } @@ -672,12 +673,12 @@ public class LdapLoginModule extends AbstractLoginModule return encryptedPassword; } - if ("{MD5}".startsWith(encryptedPassword.toUpperCase())) + if ("{MD5}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH))) { return "MD5:" + encryptedPassword.substring("{MD5}".length(), encryptedPassword.length()); } - if ("{CRYPT}".startsWith(encryptedPassword.toUpperCase())) + if ("{CRYPT}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH))) { return "CRYPT:" + encryptedPassword.substring("{CRYPT}".length(), encryptedPassword.length()); } diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java index 8c81f8654b2..2b1e346844e 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.jmx; import java.io.IOException; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; @@ -164,7 +165,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable domain = obj.getClass().getPackage().getName(); - String type = obj.getClass().getName().toLowerCase(); + String type = obj.getClass().getName().toLowerCase(Locale.ENGLISH); int dot = type.lastIndexOf('.'); if (dot >= 0) type = type.substring(dot + 1); diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java index db02cd3eb4b..2dcc9289570 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java @@ -30,6 +30,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.management.Attribute; @@ -622,7 +623,7 @@ public class ObjectMBean implements DynamicMBean convert = true; } - String uName = name.substring(0, 1).toUpperCase() + name.substring(1); + String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); Class oClass = onMBean ? this.getClass() : _managed.getClass(); LOG.debug("defineAttribute {} {}:{}:{}:{}",name,onMBean,readonly,oClass,description); @@ -862,7 +863,7 @@ public class ObjectMBean implements DynamicMBean variableName = variableName.substring(2); } - variableName = variableName.substring(0,1).toLowerCase() + variableName.substring(1); + variableName = variableName.substring(0,1).toLowerCase(Locale.ENGLISH) + variableName.substring(1); return variableName; } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java index a891f10caac..ac426630987 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java @@ -483,7 +483,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo if (connectors == null || connectors.length == 0) { //if a SystemProperty -Djetty.port= has been supplied, use that as the default port - this.connectors = new Connector[] { this.server.createDefaultConnector(server, System.getProperty(PORT_SYSPROPERTY, null)) }; + this.connectors = new Connector[] { this.server.createDefaultConnector(System.getProperty(PORT_SYSPROPERTY, null)) }; this.server.setConnectors(this.connectors); } } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java index cdd769fa5d8..8b3528678f4 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java @@ -16,58 +16,55 @@ // ======================================================================== // -/** - * - */ package org.eclipse.jetty.maven.plugin; import java.io.IOException; -public class ConsoleScanner extends Thread +public class ConsoleScanner extends Thread { - + private final AbstractJettyMojo mojo; - - public ConsoleScanner(AbstractJettyMojo mojo) + + public ConsoleScanner(AbstractJettyMojo mojo) { this.mojo = mojo; setName("Console scanner"); setDaemon(true); } - - public void run() - { - try + + public void run() + { + try { - while (true) + while (true) { checkSystemInput(); getSomeSleep(); } - } - catch (IOException e) + } + catch (IOException e) { mojo.getLog().warn(e); } } - - private void getSomeSleep() + + private void getSomeSleep() { - try + try { Thread.sleep(500); - } - catch (InterruptedException e) + } + catch (InterruptedException e) { mojo.getLog().debug(e); } } - - private void checkSystemInput() throws IOException - { + + private void checkSystemInput() throws IOException + { while (System.in.available() > 0) { int inputByte = System.in.read(); - if (inputByte >= 0) + if (inputByte >= 0) { char c = (char)inputByte; if (c == '\n') { @@ -76,12 +73,12 @@ public class ConsoleScanner extends Thread } } } - - + + /** * Skip buffered bytes of system console. */ - private void clearInputBuffer() + private void clearInputBuffer() { try { @@ -101,9 +98,9 @@ public class ConsoleScanner extends Thread catch (IOException e) { mojo.getLog().warn("Error discarding console input buffer", e); - } + } } - + private void restartWebApp() { try diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java index 8810e6f484e..9010ccff434 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java @@ -1,46 +1,45 @@ -// -// ======================================================================== -// 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; - - -/** - *

- * This goal is used to run Jetty with a pre-assembled war. - *

- *

- * It accepts exactly the same options as the run-war goal. - * However, it doesn't assume that the current artifact is a - * webapp and doesn't try to assemble it into a war before its execution. - * So using it makes sense only when used in conjunction with the - * webApp configuration parameter pointing to a pre-built WAR. - *

- *

- * This goal is useful e.g. for launching a web app in Jetty as a target for unit-tested - * HTTP client components. - *

- * - * @goal deploy-war - * @requiresDependencyResolution runtime - * @execute phase="validate" - * @description Deploy a pre-assembled war - * - */ -public class JettyDeployWar extends JettyRunWarMojo -{ -} +// +// ======================================================================== +// 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; + +/** + *

+ * This goal is used to run Jetty with a pre-assembled war. + *

+ *

+ * It accepts exactly the same options as the run-war goal. + * However, it doesn't assume that the current artifact is a + * webapp and doesn't try to assemble it into a war before its execution. + * So using it makes sense only when used in conjunction with the + * webApp configuration parameter pointing to a pre-built WAR. + *

+ *

+ * This goal is useful e.g. for launching a web app in Jetty as a target for unit-tested + * HTTP client components. + *

+ * + * @goal deploy-war + * @requiresDependencyResolution runtime + * @execute phase="validate" + * @description Deploy a pre-assembled war + * + */ +public class JettyDeployWar extends JettyRunWarMojo +{ +} diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java index 0472850a1af..a38272e09a0 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java @@ -18,23 +18,33 @@ package org.eclipse.jetty.maven.plugin; +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; @@ -42,7 +52,10 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.repository.ComponentDependency; +import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.Resource; /** @@ -64,7 +77,7 @@ import org.eclipse.jetty.util.IO; * There is a reference guide to the configuration parameters for this plugin, and more detailed information * with examples in the Configuration Guide. *

- * + * * @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; /** *

@@ -32,18 +36,18 @@ import org.eclipse.jetty.util.Scanner; *

*

* 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 + *
  • Eclipse Jetty Bug #393075
  • + *
  • Apache Tomcat Bug #54067
  • + * + * @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. + *

    + * Section 18. Fail the Physical Connection. + * + *

    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 data() + { + // Various good 1/3/9 encodings + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + data.add(new Object[]{ 0L, "00"}); + data.add(new Object[]{ 1L, "01"}); + data.add(new Object[]{ 2L, "02"}); + data.add(new Object[]{ 55L, "37"}); + data.add(new Object[]{125L, "7D"}); + + // - 3 byte tests + data.add(new Object[]{0x00_80L, "7E0080"}); + data.add(new Object[]{0x00_ABL, "7E00AB"}); + data.add(new Object[]{0x00_FFL, "7E00FF"}); + data.add(new Object[]{0x3F_FFL, "7E3FFF"}); + + // - 9 byte tests + data.add(new Object[]{0x00_00_01_FF_FFL, "7F000000000001FFFF"}); + data.add(new Object[]{0x00_00_FF_FF_FFL, "7F0000000000FFFFFF"}); + data.add(new Object[]{0x00_FF_FF_FF_FFL, "7F00000000FFFFFFFF"}); + data.add(new Object[]{0xFF_FF_FF_FF_FFL, "7F000000FFFFFFFFFF"}); + // @formatter:on + + return data; + } + + @Rule + public TestName testname = new TestName(); + + private long value; + private String expectedHex; + + public MuxGeneratorWrite139SizeTest(long value, String expectedHex) + { + this.value = value; + this.expectedHex = expectedHex; + } + + @Test + public void testWrite139Size() + { + System.err.printf("Running %s.%s - value: %,d%n",this.getClass().getName(),testname.getMethodName(),value); + ByteBuffer bbuf = ByteBuffer.allocate(10); + generator.write139Size(bbuf,value); + BufferUtil.flipToFlush(bbuf,0); + byte actual[] = BufferUtil.toArray(bbuf); + String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH); + Assert.assertThat("1/3/9 encoded size of [" + value + "]",actualHex,is(expectedHex)); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWriteChannelIdTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWriteChannelIdTest.java new file mode 100644 index 00000000000..724ea1f9b71 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxGeneratorWriteChannelIdTest.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 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; + +/** + * Tests of valid ChannelID generation + */ +@RunWith(Parameterized.class) +public class MuxGeneratorWriteChannelIdTest +{ + private static MuxGenerator generator = new MuxGenerator(); + + @Parameters + public static Collection data() + { + // Various good Channel IDs + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + data.add(new Object[]{ 0L, "00"}); + data.add(new Object[]{ 1L, "01"}); + data.add(new Object[]{ 2L, "02"}); + data.add(new Object[]{ 55L, "37"}); + data.add(new Object[]{127L, "7F"}); + + // - 2 byte tests + data.add(new Object[]{0x00_80L, "8080"}); + data.add(new Object[]{0x00_FFL, "80FF"}); + data.add(new Object[]{0x3F_FFL, "BFFF"}); + + // - 3 byte tests + data.add(new Object[]{0x00_FF_FFL, "C0FFFF"}); + data.add(new Object[]{0x1F_FF_FFL, "DFFFFF"}); + + // - 3 byte tests + data.add(new Object[]{0x00_FF_FF_FFL, "E0FFFFFF"}); + data.add(new Object[]{0x1F_FF_FF_FFL, "FFFFFFFF"}); + + // @formatter:on + return data; + } + + @Rule + public TestName testname = new TestName(); + + private long channelId; + private String expectedHex; + + public MuxGeneratorWriteChannelIdTest(long channelId, String expectedHex) + { + this.channelId = channelId; + this.expectedHex = expectedHex; + } + + @Test + public void testReadChannelId() + { + System.err.printf("Running %s.%s - channelId: %,d%n",this.getClass().getName(),testname.getMethodName(),channelId); + ByteBuffer bbuf = ByteBuffer.allocate(10); + generator.writeChannelId(bbuf,channelId); + BufferUtil.flipToFlush(bbuf,0); + byte actual[] = BufferUtil.toArray(bbuf); + String actualHex = TypeUtil.toHexString(actual).toUpperCase(Locale.ENGLISH); + Assert.assertThat("Channel ID [" + channelId + "]",actualHex,is(expectedHex)); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxInjector.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxInjector.java new file mode 100644 index 00000000000..d3d9170e3fe --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxInjector.java @@ -0,0 +1,64 @@ +// +// ======================================================================== +// 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 org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.core.io.IncomingFrames; +import org.eclipse.jetty.websocket.core.io.OutgoingFrames; +import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame; + +/** + * Helpful utility class to send arbitrary mux events into a physical connection's IncomingFrames. + * + * @see MuxReducer + */ +public class MuxInjector implements OutgoingFrames +{ + private static final Logger LOG = Log.getLogger(MuxInjector.class); + private IncomingFrames incoming; + private MuxGenerator generator; + + public MuxInjector(IncomingFrames incoming) + { + this.incoming = incoming; + this.generator = new MuxGenerator(); + this.generator.setOutgoing(this); + } + + public void frame(long channelId, WebSocketFrame frame) throws IOException + { + this.generator.generate(channelId,frame); + } + + public void op(MuxControlBlock op) throws IOException + { + this.generator.generate(op); + } + + @Override + public void output(C context, Callback callback, WebSocketFrame frame) throws IOException + { + LOG.debug("Injecting {} to {}",frame,incoming); + this.incoming.incoming(frame); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRFCTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRFCTest.java new file mode 100644 index 00000000000..ca1a557089e --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRFCTest.java @@ -0,0 +1,232 @@ +// +// ======================================================================== +// 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.websocket.core.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.core.protocol.IncomingFramesCapture; +import org.eclipse.jetty.websocket.core.protocol.OpCode; +import org.eclipse.jetty.websocket.core.protocol.Parser; +import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame; +import org.junit.Assert; +import org.junit.Test; + +public class MuxParserRFCTest +{ + private LinkedList asFrames(byte[] buf) + { + IncomingFramesCapture capture = new IncomingFramesCapture(); + WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); + Parser parser = new Parser(policy); + parser.setIncomingFramesHandler(capture); + List muxList = Collections.singletonList(new MuxExtension()); + parser.configureFromExtensions(muxList); + ByteBuffer bbuf = ByteBuffer.wrap(buf); + parser.parse(bbuf); + + return capture.getFrames(); + } + + private boolean isHexOnly(String part) + { + Pattern bytePat = Pattern.compile("(\\s*0x[0-9A-Fa-f]{2}+){1,}+"); + Matcher mat = bytePat.matcher(part); + return mat.matches(); + } + + private MuxEventCapture parseMuxFrames(LinkedList frames) + { + MuxParser parser = new MuxParser(); + MuxEventCapture capture = new MuxEventCapture(); + parser.setEvents(capture); + for(WebSocketFrame frame: frames) { + parser.parse(frame); + } + return capture; + } + + @Test + public void testIsHexOnly() + { + Assert.assertTrue(isHexOnly("0x00")); + Assert.assertTrue(isHexOnly("0x00 0xaF")); + Assert.assertFalse(isHexOnly("Hello World")); + } + + @Test + public void testRFCExample1() throws IOException + { + // Create RFC detailed frames + byte buf[] = toByteArray("0x82 0x0d 0x01 0x81","Hello world"); + LinkedList frames = asFrames(buf); + Assert.assertThat("Frame count",frames.size(),is(1)); + + // Have mux parse frames + MuxEventCapture capture = parseMuxFrames(frames); + capture.assertFrameCount(1); + + MuxedFrame mux; + + mux = capture.getFrames().pop(); + String prefix = "MuxFrame[0]"; + Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L)); + Assert.assertThat(prefix + ".fin",mux.isFin(),is(true)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT)); + + String payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is("Hello world")); + } + + @Test + public void testRFCExample2() throws IOException + { + // Create RFC detailed frames + byte buf[] = toByteArray("0x02 0x07 0x01 0x81","Hello","0x80 0x06"," world"); + LinkedList frames = asFrames(buf); + Assert.assertThat("Frame count",frames.size(),is(2)); + + // Have mux parse frames + MuxEventCapture capture = parseMuxFrames(frames); + capture.assertFrameCount(2); + + MuxedFrame mux; + + // Text Frame + mux = capture.getFrames().get(0); + String prefix = "MuxFrame[0]"; + Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L)); + // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(false)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT)); + + String payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is("Hello")); + + // Continuation Frame + mux = capture.getFrames().get(1); + prefix = "MuxFrame[1]"; + // (BUG IN DRAFT) Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L)); + // (BUG IN DRAFT) Assert.assertThat(prefix + ".fin",mux.isFin(),is(true)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(true)); + // (BUG IN DRAFT) Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.BINARY)); + + payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is(" world")); + } + + @Test + public void testRFCExample3() throws IOException + { + // Create RFC detailed frames + byte buf[] = toByteArray("0x82 0x07 0x01 0x01","Hello","0x82 0x05 0x02 0x81","bye","0x82 0x08 0x01 0x80"," world"); + LinkedList frames = asFrames(buf); + Assert.assertThat("Frame count",frames.size(),is(3)); + + // Have mux parse frames + MuxEventCapture capture = parseMuxFrames(frames); + capture.assertFrameCount(3); + + MuxedFrame mux; + + // Text Frame (Message 1) + mux = capture.getFrames().pop(); + String prefix = "MuxFrame[0]"; + Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L)); + Assert.assertThat(prefix + ".fin",mux.isFin(),is(false)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(false)); + Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT)); + + String payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is("Hello")); + + // Text Frame (Message 2) + mux = capture.getFrames().pop(); + prefix = "MuxFrame[1]"; + Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(2L)); + Assert.assertThat(prefix + ".fin",mux.isFin(),is(true)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(false)); + Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT)); + + payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is("bye")); + + // Continuation Frame (Message 1) + mux = capture.getFrames().pop(); + prefix = "MuxFrame[2]"; + Assert.assertThat(prefix + ".channelId",mux.getChannelId(),is(1L)); + Assert.assertThat(prefix + ".fin",mux.isFin(),is(true)); + Assert.assertThat(prefix + ".rsv1",mux.isRsv1(),is(false)); + Assert.assertThat(prefix + ".rsv2",mux.isRsv2(),is(false)); + Assert.assertThat(prefix + ".rsv3",mux.isRsv3(),is(false)); + Assert.assertThat(prefix + ".masked",mux.isMasked(),is(false)); + Assert.assertThat(prefix + ".continuation",mux.isContinuation(),is(true)); + Assert.assertThat(prefix + ".opcode",mux.getOpCode(),is(OpCode.TEXT)); + + payload = mux.getPayloadAsUTF8(); + Assert.assertThat(prefix + ".payload/text",payload,is(" world")); + } + + private byte[] toByteArray(String... parts) throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for(String part: parts) { + if (isHexOnly(part)) + { + String hexonly = part.replaceAll("\\s*0x",""); + out.write(TypeUtil.fromHexString(hexonly)); + } + else + { + out.write(part.getBytes(StringUtil.__UTF8_CHARSET)); + } + } + return out.toByteArray(); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_BadEncodingTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_BadEncodingTest.java new file mode 100644 index 00000000000..cfcf1e89fa6 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_BadEncodingTest.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; + +import static org.hamcrest.Matchers.*; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +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; + +/** + * Tests for bad 1/3/9 size encoding. + */ +@RunWith(Parameterized.class) +public class MuxParserRead139Size_BadEncodingTest +{ + private static MuxParser parser = new MuxParser(); + + @Parameters + public static Collection data() + { + // Various bad 1/3/9 encodings + // Violating "minimal number of bytes necessary" rule. + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + // all known 1 byte tests are valid + + // - 3 byte tests + data.add(new Object[]{"7E0000"}); + data.add(new Object[]{"7E0001"}); + data.add(new Object[]{"7E0012"}); + data.add(new Object[]{"7E0059"}); + // extra bytes (not related to 1/3/9 size) + data.add(new Object[]{"7E0012345678"}); + + // - 9 byte tests + data.add(new Object[]{"7F0000000000000000"}); + data.add(new Object[]{"7F0000000000000001"}); + data.add(new Object[]{"7F0000000000000012"}); + data.add(new Object[]{"7F0000000000001234"}); + data.add(new Object[]{"7F000000000000FFFF"}); + + // @formatter:on + return data; + } + + @Rule + public TestName testname = new TestName(); + + private String rawhex; + private byte buf[]; + + public MuxParserRead139Size_BadEncodingTest(String rawhex) + { + this.rawhex = rawhex; + this.buf = TypeUtil.fromHexString(rawhex); + } + + @Test + public void testRead139EncodedSize() + { + System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex); + ByteBuffer bbuf = ByteBuffer.wrap(buf); + try + { + parser.read139EncodedSize(bbuf); + // unexpected path + Assert.fail("Should have failed with an invalid parse"); + } + catch (MuxException e) + { + // expected path + Assert.assertThat(e.getMessage(),containsString("Invalid 1/3/9 length")); + } + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_GoodTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_GoodTest.java new file mode 100644 index 00000000000..b0a0b44932f --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserRead139Size_GoodTest.java @@ -0,0 +1,99 @@ +// +// ======================================================================== +// 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 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 MuxParserRead139Size_GoodTest +{ + private static MuxParser parser = new MuxParser(); + + @Parameters + public static Collection data() + { + // Various good 1/3/9 encodings + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + data.add(new Object[]{"00", 0L}); + data.add(new Object[]{"01", 1L}); + data.add(new Object[]{"02", 2L}); + data.add(new Object[]{"37", 55L}); + data.add(new Object[]{"7D", 125L}); + // extra bytes (not related to 1/3/9 size) + data.add(new Object[]{"37FF", 55L}); + data.add(new Object[]{"0123456789", 0x01L}); + + // - 3 byte tests + data.add(new Object[]{"7E0080", 0x00_80L}); + data.add(new Object[]{"7E00AB", 0x00_ABL}); + data.add(new Object[]{"7E00FF", 0x00_FFL}); + data.add(new Object[]{"7E3FFF", 0x3F_FFL}); + // extra bytes (not related to 1/3/9 size) + data.add(new Object[]{"7E0123456789", 0x01_23L}); + + // - 9 byte tests + data.add(new Object[]{"7F000000000001FFFF", 0x00_00_01_FF_FFL}); + data.add(new Object[]{"7F0000000000FFFFFF", 0x00_00_FF_FF_FFL}); + data.add(new Object[]{"7F00000000FFFFFFFF", 0x00_FF_FF_FF_FFL}); + data.add(new Object[]{"7F000000FFFFFFFFFF", 0xFF_FF_FF_FF_FFL}); + + // @formatter:on + return data; + } + + @Rule + public TestName testname = new TestName(); + + private String rawhex; + private byte buf[]; + private long expected; + + public MuxParserRead139Size_GoodTest(String rawhex, long expected) + { + this.rawhex = rawhex; + this.buf = TypeUtil.fromHexString(rawhex); + this.expected = expected; + } + + @Test + public void testRead139EncodedSize() + { + System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex); + ByteBuffer bbuf = ByteBuffer.wrap(buf); + long actual = parser.read139EncodedSize(bbuf); + Assert.assertThat("1/3/9 size from buffer [" + rawhex + "]",actual,is(expected)); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java new file mode 100644 index 00000000000..22fd4dfb738 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_BadEncodingTest.java @@ -0,0 +1,106 @@ +// +// ======================================================================== +// 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 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; + +/** + * Tests of Invalid ChannelID parsing + */ +@RunWith(Parameterized.class) +public class MuxParserReadChannelId_BadEncodingTest +{ + private static MuxParser parser = new MuxParser(); + + @Parameters + public static Collection data() + { + // Various Invalid Encoded Channel IDs. + // Violating "minimal number of bytes necessary" rule. + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + // all known 1 byte tests are valid + + // - 2 byte tests + data.add(new Object[]{"8000"}); + data.add(new Object[]{"8001"}); + data.add(new Object[]{"807F"}); + // extra bytes (not related to channelId) + data.add(new Object[]{"8023456789"}); + + // - 3 byte tests + data.add(new Object[]{"C00000"}); + data.add(new Object[]{"C01234"}); + data.add(new Object[]{"C03FFF"}); + + // - 3 byte tests + data.add(new Object[]{"E0000000"}); + data.add(new Object[]{"E0000001"}); + data.add(new Object[]{"E01FFFFF"}); + + // @formatter:on + return data; + } + + @Rule + public TestName testname = new TestName(); + + private String rawhex; + private byte buf[]; + + public MuxParserReadChannelId_BadEncodingTest(String rawhex) + { + this.rawhex = rawhex; + this.buf = TypeUtil.fromHexString(rawhex); + } + + @Test + public void testBadEncoding() + { + System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex); + ByteBuffer bbuf = ByteBuffer.wrap(buf); + try + { + parser.readChannelId(bbuf); + // unexpected path + Assert.fail("Should have failed with an invalid parse"); + } + catch (MuxException e) + { + // expected path + Assert.assertThat(e.getMessage(),containsString("Invalid Channel ID")); + } + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_GoodTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_GoodTest.java new file mode 100644 index 00000000000..d8b2b82461e --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxParserReadChannelId_GoodTest.java @@ -0,0 +1,106 @@ +// +// ======================================================================== +// 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 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; + +/** + * Tests of valid ChannelID parsing + */ +@RunWith(Parameterized.class) +public class MuxParserReadChannelId_GoodTest +{ + private static MuxParser parser = new MuxParser(); + + @Parameters + public static Collection data() + { + // Various good Channel IDs + List data = new ArrayList<>(); + + // @formatter:off + // - 1 byte tests + data.add(new Object[]{"00", 0L}); + data.add(new Object[]{"01", 1L}); + data.add(new Object[]{"02", 2L}); + data.add(new Object[]{"7F", 127L}); + // extra bytes (not related to channelId) + data.add(new Object[]{"37FF", 55L}); + data.add(new Object[]{"0123456789", 0x01L}); + + // - 2 byte tests + data.add(new Object[]{"8080", 0x00_80L}); + data.add(new Object[]{"80FF", 0x00_FFL}); + data.add(new Object[]{"BFFF", 0x3F_FFL}); + // extra bytes (not related to channelId) + data.add(new Object[]{"8123456789", 0x01_23L}); + + // - 3 byte tests + data.add(new Object[]{"C0FFFF", 0x00_FF_FFL}); + data.add(new Object[]{"DFFFFF", 0x1F_FF_FFL}); + // extra bytes (not related to channelId) + data.add(new Object[]{"C123456789", 0x01_23_45L}); + + // - 3 byte tests + data.add(new Object[]{"E0FFFFFF", 0x00_FF_FF_FFL}); + data.add(new Object[]{"FFFFFFFF", 0x1F_FF_FF_FFL}); + // extra bytes (not related to channelId) + data.add(new Object[]{"E123456789", 0x01_23_45_67L}); + + // @formatter:on + return data; + } + + @Rule + public TestName testname = new TestName(); + + private String rawhex; + private byte buf[]; + private long expected; + + public MuxParserReadChannelId_GoodTest(String rawhex, long expected) + { + this.rawhex = rawhex; + this.buf = TypeUtil.fromHexString(rawhex); + this.expected = expected; + } + + @Test + public void testReadChannelId() + { + System.err.printf("Running %s.%s - hex: %s%n",this.getClass().getName(),testname.getMethodName(),rawhex); + ByteBuffer bbuf = ByteBuffer.wrap(buf); + long actual = parser.readChannelId(bbuf); + Assert.assertThat("Channel ID from buffer [" + rawhex + "]",actual,is(expected)); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxReducer.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxReducer.java new file mode 100644 index 00000000000..f9f46766c26 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/MuxReducer.java @@ -0,0 +1,48 @@ +// +// ======================================================================== +// 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 org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.websocket.core.io.OutgoingFrames; +import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame; + +/** + * Helpful utility class to parse arbitrary mux events from a physical connection's OutgoingFrames. + * + * @see MuxInjector + */ +public class MuxReducer extends MuxEventCapture implements OutgoingFrames +{ + private MuxParser parser; + + public MuxReducer() + { + parser = new MuxParser(); + parser.setEvents(this); + } + + @Override + public void output(C context, Callback callback, WebSocketFrame frame) throws IOException + { + parser.parse(frame); + callback.completed(context); // let blocked calls know the send is complete. + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/DummyMuxAddServer.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/DummyMuxAddServer.java new file mode 100644 index 00000000000..4c5c8599189 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/DummyMuxAddServer.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// 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.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.core.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.core.examples.echo.AdapterEchoSocket; +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; +import org.eclipse.jetty.websocket.core.io.event.EventDriver; +import org.eclipse.jetty.websocket.core.io.event.EventDriverFactory; + +/** + * Dummy impl of MuxAddServer + */ +public class DummyMuxAddServer implements MuxAddServer +{ + @SuppressWarnings("unused") + private static final Logger LOG = Log.getLogger(DummyMuxAddServer.class); + private AdapterEchoSocket echo; + private WebSocketPolicy policy; + private EventDriverFactory eventDriverFactory; + + public DummyMuxAddServer() + { + this.policy = WebSocketPolicy.newServerPolicy(); + this.eventDriverFactory = new EventDriverFactory(policy); + this.echo = new AdapterEchoSocket(); + } + + @Override + public String handshake(MuxChannel channel, String requestHandshake) throws MuxException, IOException + { + StringBuilder response = new StringBuilder(); + response.append("HTTP/1.1 101 Switching Protocols\r\n"); + response.append("Connection: upgrade\r\n"); + // not meaningful (per Draft 08) hresp.append("Upgrade: websocket\r\n"); + // not meaningful (per Draft 08) hresp.append("Sec-WebSocket-Accept: Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n"); + response.append("\r\n"); + + EventDriver websocket = this.eventDriverFactory.wrap(echo); + WebSocketSession session = new WebSocketSession(websocket,channel,channel.getPolicy(),"echo"); + channel.setSession(session); + channel.setSubProtocol("echo"); + channel.onOpen(); + session.onConnect(); + + return response.toString(); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxerAddServerTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxerAddServerTest.java new file mode 100644 index 00000000000..fc8608c2b4a --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/extensions/mux/add/MuxerAddServerTest.java @@ -0,0 +1,93 @@ +// +// ======================================================================== +// 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 static org.hamcrest.Matchers.*; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.core.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.core.extensions.mux.MuxInjector; +import org.eclipse.jetty.websocket.core.extensions.mux.MuxOp; +import org.eclipse.jetty.websocket.core.extensions.mux.MuxReducer; +import org.eclipse.jetty.websocket.core.extensions.mux.Muxer; +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.io.LocalWebSocketConnection; +import org.eclipse.jetty.websocket.core.protocol.OpCode; +import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +public class MuxerAddServerTest +{ + @Rule + public TestName testname = new TestName(); + + @Test + public void testAddChannel_Server() throws Exception + { + LocalWebSocketConnection physical = new LocalWebSocketConnection(testname); + physical.setPolicy(WebSocketPolicy.newServerPolicy()); + physical.onOpen(); + + MuxReducer reducer = new MuxReducer(); + + // Represents a server side muxer. + Muxer muxer = new Muxer(physical,reducer); + DummyMuxAddServer addServer = new DummyMuxAddServer(); + muxer.setAddServer(addServer); + + MuxInjector inject = new MuxInjector(muxer); + + // Trigger AddChannel + StringBuilder request = new StringBuilder(); + request.append("GET /echo HTTP/1.1\r\n"); + request.append("Host: localhost\r\n"); + request.append("Upgrade: websocket\r\n"); + request.append("Connection: Upgrade\r\n"); + request.append("Sec-WebSocket-Key: ZDTIRU5vU9xOfkg8JAgN3A==\r\n"); + request.append("Sec-WebSocket-Version: 13\r\n"); + request.append("\r\n"); + + MuxAddChannelRequest req = new MuxAddChannelRequest(); + req.setChannelId(1); + req.setEnc((byte)0); + req.setHandshake(BufferUtil.toBuffer(request.toString())); + + inject.op(req); + + // Make sure we got AddChannelResponse + reducer.assertHasOp(MuxOp.ADD_CHANNEL_RESPONSE,1); + MuxAddChannelResponse response = (MuxAddChannelResponse)reducer.getOps().pop(); + Assert.assertThat("AddChannelResponse.channelId",response.getChannelId(),is(1L)); + Assert.assertThat("AddChannelResponse.failed",response.isFailed(),is(false)); + Assert.assertThat("AddChannelResponse.handshake",response.getHandshake(),notNullValue()); + Assert.assertThat("AddChannelResponse.handshakeSize",response.getHandshakeSize(),is(57L)); + + reducer.reset(); + + // Send simple echo request + inject.frame(1,WebSocketFrame.text("Hello World")); + + // Test for echo response (is there a user echo websocket connected to the sub-channel?) + reducer.assertHasFrame(OpCode.TEXT,1L,1); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/io/LocalWebSocketConnection.java index 64157c5f1b2..e69341c1892 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/io/LocalWebSocketConnection.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/io/LocalWebSocketConnection.java @@ -32,6 +32,8 @@ import org.junit.rules.TestName; public class LocalWebSocketConnection implements WebSocketConnection { private final String id; + private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); + private boolean open = false; public LocalWebSocketConnection() { @@ -51,22 +53,25 @@ public class LocalWebSocketConnection implements WebSocketConnection @Override public void close() { + open = false; } @Override public void close(int statusCode, String reason) { + open = false; } @Override public void disconnect() { + open = false; } @Override public WebSocketPolicy getPolicy() { - return null; + return policy; } @Override @@ -97,10 +102,9 @@ public class LocalWebSocketConnection implements WebSocketConnection @Override public boolean isOpen() { - return false; + return open; } - @Override public boolean isOutputClosed() { @@ -120,11 +124,20 @@ public class LocalWebSocketConnection implements WebSocketConnection // TODO Auto-generated method stub } + public void onOpen() { + open = true; + } + @Override public void ping(C context, Callback callback, byte[] payload) throws IOException { } + public void setPolicy(WebSocketPolicy policy) + { + this.policy = policy; + } + @Override public SuspendToken suspend() { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java index 2ce24d94682..0ed4af371c8 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/protocol/OutgoingNetworkBytesCapture.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -49,8 +50,8 @@ public class OutgoingNetworkBytesCapture implements OutgoingFrames { Assert.assertThat("Capture index does not exist",idx,lessThan(captured.size())); ByteBuffer buf = captured.get(idx); - String actualHex = TypeUtil.toHexString(BufferUtil.toArray(buf)).toUpperCase(); - Assert.assertThat("captured[" + idx + "]",actualHex,is(expectedHex.toUpperCase())); + String actualHex = TypeUtil.toHexString(BufferUtil.toArray(buf)).toUpperCase(Locale.ENGLISH); + Assert.assertThat("captured[" + idx + "]",actualHex,is(expectedHex.toUpperCase(Locale.ENGLISH))); } public List getCaptured() diff --git a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties index e7cebead1af..a40bcc9b25c 100644 --- a/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-core/src/test/resources/jetty-logging.properties @@ -1,5 +1,5 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.websocket.LEVEL=WARN +org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG # org.eclipse.jetty.websocket.protocol.LEVEL=DEBUG # org.eclipse.jetty.websocket.io.payload.LEVEL=DEBUG diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java index 6f727834596..7c45d6ba87a 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java @@ -25,6 +25,7 @@ import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -280,7 +281,7 @@ public class Fuzzer // early socket close can propagate back to the client // before it has a chance to finish writing out the // remaining frame octets - Assert.assertThat("Allowed to be a broken pipe",ignore.getMessage().toLowerCase(),containsString("broken pipe")); + Assert.assertThat("Allowed to be a broken pipe",ignore.getMessage().toLowerCase(Locale.ENGLISH),containsString("broken pipe")); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java index c03b63c60b0..6ed2c25fc77 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Locale; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.StringUtil; @@ -68,7 +69,7 @@ public class BrowserSocket int idx = message.indexOf(':'); if (idx > 0) { - String key = message.substring(0,idx).toLowerCase(); + String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH); String val = message.substring(idx + 1); switch (key) { diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java index 78776b2a987..58521cc8956 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -40,6 +40,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Queue; @@ -420,7 +421,7 @@ public class XmlConfiguration private void set(Object obj, XmlParser.Node node) throws Exception { String attr = node.getAttribute("name"); - String name = "set" + attr.substring(0,1).toUpperCase() + attr.substring(1); + String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1); Object value = value(obj,node); Object[] arg = { value }; @@ -623,7 +624,7 @@ public class XmlConfiguration try { // try calling a getXxx method. - Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase() + name.substring(1),(java.lang.Class[])null); + Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase(Locale.ENGLISH) + name.substring(1),(java.lang.Class[])null); obj = method.invoke(obj,(java.lang.Object[])null); configure(obj,node,0); } @@ -1172,7 +1173,7 @@ public class XmlConfiguration Object[] obj = new Object[args.length]; for (int i = 0; i < args.length; i++) { - if (args[i].toLowerCase().endsWith(".properties")) + if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties")) { properties.load(Resource.newResource(args[i]).getInputStream()); } diff --git a/pom.xml b/pom.xml index 4319a41f38e..df1ca303b0b 100644 --- a/pom.xml +++ b/pom.xml @@ -413,6 +413,7 @@ jetty-annotations jetty-jndi jetty-jsp + jetty-jaas jetty-distribution jetty-spring jetty-client diff --git a/test-jetty-webapp/pom.xml b/test-jetty-webapp/pom.xml index c887a0dfc14..027a7974281 100644 --- a/test-jetty-webapp/pom.xml +++ b/test-jetty-webapp/pom.xml @@ -93,9 +93,8 @@ - + -org.eclipse.jetty.util. + -org.eclipse.jetty.servlets. test webapp is deployed. DO NOT USE IN PRODUCTION! diff --git a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java index e4372b6f9f9..f138697b3b3 100644 --- a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java +++ b/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java @@ -165,7 +165,7 @@ public class TestServer server.setSendServerVersion(true); WebAppContext webapp = new WebAppContext(); - webapp.setParentLoaderPriority(true); + //webapp.setParentLoaderPriority(true); webapp.setResourceBase("./src/main/webapp"); webapp.setAttribute("testAttribute","testValue"); File sessiondir=File.createTempFile("sessions",null); diff --git a/tests/pom.xml b/tests/pom.xml index ea72f4de254..5d2065c0eb9 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -40,9 +40,13 @@ + test-webapps test-sessions + diff --git a/tests/test-integration/src/test/resources/RFC2616Base.xml b/tests/test-integration/src/test/resources/RFC2616Base.xml index 878b1291129..f791c1af004 100644 --- a/tests/test-integration/src/test/resources/RFC2616Base.xml +++ b/tests/test-integration/src/test/resources/RFC2616Base.xml @@ -95,7 +95,7 @@ /webapp-contexts/RFC2616 0 - + /testable-jetty-server-config.properties diff --git a/tests/test-sessions/test-hash-sessions/pom.xml b/tests/test-sessions/test-hash-sessions/pom.xml index 0d3797c7b61..ac5c32fe601 100644 --- a/tests/test-sessions/test-hash-sessions/pom.xml +++ b/tests/test-sessions/test-hash-sessions/pom.xml @@ -68,7 +68,8 @@ org.eclipse.jetty.toolchain jetty-test-helper - test + + compile diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index 50493194414..ea792ab117c 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -40,5 +40,8 @@ test-webapp-rfc2616 + test-mock-resources + test-servlet-spec + test-jaas-webapp diff --git a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml index ca9e00e028e..04effeab037 100644 --- a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml +++ b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml @@ -10,7 +10,7 @@ src/main/config - contexts/** + webapps/test-jaas.xml etc/** diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/contexts/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml similarity index 93% rename from tests/test-webapps/test-jaas-webapp/src/main/config/contexts/test-jaas.xml rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml index 5b71dadbb16..8a94b929245 100644 --- a/tests/test-webapps/test-jaas-webapp/src/main/config/contexts/test-jaas.xml +++ b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml @@ -7,7 +7,8 @@ /test-jaas - /webapps/test-jaas + /webapps/test-jaas.war + true diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..c469f742375 --- /dev/null +++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,8 @@ + + + + + + test-jaas webapp is deployed. DO NOT USE IN PRODUCTION! + + diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html index 4c6e0d12edf..62b744fa650 100644 --- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html +++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html @@ -1,4 +1,15 @@ -Welcome to the JAAS Authentication and Authorization Test + + + JAAS Authentication and Authorization Test + + + + + @@ -6,6 +17,7 @@ Home


    + Test Web Application Only - Do NOT Deploy in Production

    JAAS Authentication and Authorization Demo

    @@ -20,7 +32,6 @@
  • Unjar the test-jaas-webapp-<version>-config.jar inside $JETTY_HOME. The following files will be added:
    -       etc/jetty-jaas.xml
            etc/login.conf
            etc/login.properties
            contexts/test-jaas.xml
    @@ -37,7 +48,7 @@
       Click on the following link to test JAAS authentication and role-based web security constraint authorization.
       

    - 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("

    Jetty JNDI Tests

    "); + out.println(""); + + out.println("

    Injection and JNDI Lookup Results

    "); + out.println("

    "+resourceNameMappingInjectionResult+"

    "); + out.println("

    "+envEntryOverrideResult+"

    "); + out.println("

    "+postConstructResult+"

    "); + out.println("

    "+preDestroyResult+"

    "); + out.println("

    "+envEntryGlobalScopeResult+"

    "); + out.println("

    "+envEntryWebAppScopeResult+"

    "); + out.println("

    "+userTransactionResult+"

    "); + out.println("

    "+mailSessionResult+"

    "); + + + out.println(""); + out.println(""); + out.flush(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + + + public void destroy () + { + } +} diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml new file mode 100644 index 00000000000..b1b8f0e3224 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml @@ -0,0 +1,47 @@ + + + + + woggle + 4000 + false + + + + + + wiggle + 100 + true + + + + + + mail/Session + + + CHANGE-ME + CHANGE-ME + + + false + CHANGE-ME + CHANGE-ME + false + + + + + + + + + + jdbc/mydatasource + + + + + + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml new file mode 100644 index 00000000000..477c9ca2e82 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + org.eclipse.jetty.webapp.WebInfConfiguration + org.eclipse.jetty.webapp.WebXmlConfiguration + org.eclipse.jetty.webapp.MetaInfConfiguration + org.eclipse.jetty.webapp.FragmentConfiguration + org.eclipse.jetty.plus.webapp.EnvConfiguration + org.eclipse.jetty.plus.webapp.PlusConfiguration + org.eclipse.jetty.annotations.AnnotationConfiguration + org.eclipse.jetty.webapp.JettyWebXmlConfiguration + org.eclipse.jetty.webapp.TagLibConfiguration + + + + + /test-jndi + /webapps/test-jndi.war + true + false + true + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml new file mode 100644 index 00000000000..252251b16e6 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml new file mode 100644 index 00000000000..8adbfae71c3 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml @@ -0,0 +1,34 @@ + + + + + + + + + gargle + 100 + true + + + + + + wiggle + 55.0 + true + + + + + + jdbc/mydatasource1 + jdbc/mydatasource + + + + + + + + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..f7207c25b02 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,8 @@ + + + + + + test-jndi webapp is deployed. DO NOT USE IN PRODUCTION! + + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..3c434672a24 --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,61 @@ + + + + Test JNDI WebApp + + + JNDITest + com.acme.JNDITest + 1 + + + + JNDITest + /test/* + + + + wiggle + 99.99 + java.lang.Double + + com.acme.JNDITest + wiggle + + + + + mail/Session + javax.mail.Session + Container + + + + jdbc/mydatasource1 + javax.sql.DataSource + Container + + com.acme.JNDITest + myDatasource + + + + + + com.acme.JNDITest + postConstruct + + + + com.acme.JNDITest + preDestroy + + + + + diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/jetty_banner.gif b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/jetty_banner.gif new file mode 100644 index 00000000000..0a9b1e019ba Binary files /dev/null and b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/jetty_banner.gif differ diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif new file mode 100644 index 00000000000..c5dd44319f0 Binary files /dev/null and b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif differ diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html new file mode 100644 index 00000000000..ddb4ac7908b --- /dev/null +++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html @@ -0,0 +1,63 @@ + + + JNDI Test WebApp + + + + + + + +

     

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

    +

    +

      +
    1. 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
      +     
      +
    2. +
    3. Edit your $JETTY_HOME/start.ini file and add the following lines: +
      +      OPTIONS=plus
      +      OPTIONS=jta
      +      
      +
    4. +
    +

    + +

    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 + 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 + 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("

    Results

    "); + + out.println("

    Init Params from Annotation

    "); + out.println("
    ");
    +            out.println("initParams={@WebInitParam(name=\"fromAnnotation\", value=\"xyz\")}");
    +            out.println("
    "); + out.println("
    Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "PASS": "FAIL")); + + out.println("

    Init Params from web-fragment

    "); + out.println("
    ");
    +            out.println("extra1=123, extra2=345");
    +            out.println("
    "); + boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2")); + out.println("
    Result: "+(fragInitParamResult? "PASS": "FAIL")); + + + __HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet", + "javax.servlet.http.HttpServlet", + "com.acme.AnnotationTest", + "com.acme.RoleAnnotationTest", + "com.acme.MultiPartTest", + "com.acme.FragmentServlet", + "com.acme.TestListener", + "com.acme.SecuredServlet", + "com.acme.Bar"); + out.println("

    @ContainerInitializer

    "); + out.println("
    ");
    +             out.println("@HandlesTypes({javax.servlet.Servlet.class, Foo.class})");
    +             out.println("
    "); + out.print("
    Result: "); + List classes = (List)config.getServletContext().getAttribute("com.acme.Foo"); + List classNames = new ArrayList(); + if (classes != null) + { + for (Class c: classes) + { + classNames.add(c.getName()); + out.print(c.getName()+" "); + } + + if (classNames.size() != __HandlesTypes.size()) + out.println("
    FAIL"); + else if (!classNames.containsAll(__HandlesTypes)) + out.println("
    FAIL"); + else + out.println("
    PASS"); + } + else + out.print("
    FAIL (No such attribute com.acme.Foo)"); + out.println("
    "); + + out.println("

    Complete Servlet Registration

    "); + Boolean complete = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.complete"); + out.println("
    Result: "+(complete.booleanValue()?"PASS":"FAIL")+""); + + out.println("

    @PostConstruct Callback

    "); + out.println("
    ");
    +            out.println("@PostConstruct");
    +            out.println("private void myPostConstructMethod ()");
    +            out.println("{}"); 
    +            out.println("
    "); + out.println("
    Result: "+postConstructResult+""); + + + out.println("

    @Resource Injection for DataSource

    "); + out.println("
    ");         
    +            out.println("@Resource(mappedName=\"jdbc/mydatasource\");");
    +            out.println("public void setMyDatasource(DataSource ds)");
    +            out.println("{");
    +            out.println("myDS=ds;");
    +            out.println("}");
    +            out.println("
    "); + out.println("
    Result: "+dsResult+""); + out.println("
    JNDI Lookup Result: "+dsLookupResult+""); + + + out.println("

    @Resource Injection for env-entry

    "); + out.println("
    ");
    +            out.println("@Resource(mappedName=\"maxAmount\")");
    +            out.println("private Double maxAmount;");
    +            out.println("@Resource(name=\"minAmount\")");
    +            out.println("private Double minAmount;");
    +            out.println("
    "); + out.println("
    Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" PASS":" FAIL")+""); + out.println("
    JNDI Lookup Result: "+envLookupResult+""); + out.println("
    Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" PASS":" FAIL")+""); + out.println("
    JNDI Lookup Result: "+envLookupResult2+""); + out.println("
    Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" PASS":" FAIL")+""); + out.println("
    JNDI Lookup Result: "+envLookupResult3+""); + out.println("

    @Resource Injection for UserTransaction

    "); + out.println("
    ");
    +            out.println("@Resource(mappedName=\"UserTransaction\")");
    +            out.println("private UserTransaction myUserTransaction;");
    +            out.println("
    "); + out.println("
    Result: "+txResult+""); + out.println("
    JNDI Lookup Result: "+txLookupResult+""); + out.println("

    DeclaresRoles

    "); + out.println("

    Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @DeclareRoles annotation

    "); + String context = request.getContextPath(); + if (!context.endsWith("/")) + context += "/"; + context += "role/"; + out.println("
    "); + + out.println("

    ServletSecurity

    "); + out.println("

    Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @ServletSecurity annotation

    "); + context = request.getContextPath(); + if (!context.endsWith("/")) + context += "/"; + context += "sec/foo"; + out.println("
    "); + + + out.println(""); + out.println(""); + out.flush(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + + + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/Bar.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/Bar.java new file mode 100644 index 00000000000..264d5359c38 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/Bar.java @@ -0,0 +1,25 @@ +//======================================================================== +//Copyright 2010 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 com.acme.Foo; + +public class Bar { + + @Foo(2) + public void someMethod () { + } + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockDataSource.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockDataSource.java new file mode 100644 index 00000000000..c64319a4b23 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockDataSource.java @@ -0,0 +1,109 @@ +//======================================================================== +//$Id: MockDataSource.java 3317 2008-07-19 08:08:24Z gregw $ +//Copyright 2006 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.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() + { + // TODO Auto-generated method stub + return null; + } + + /** + * @see javax.sql.DataSource#getConnection() + */ + public Connection getConnection() throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + /** + * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) + */ + public Connection getConnection(String username, String password) + throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + /** + * @see javax.sql.DataSource#getLogWriter() + */ + public PrintWriter getLogWriter() throws SQLException + { + // TODO Auto-generated method stub + return null; + } + + /** + * @see javax.sql.DataSource#getLoginTimeout() + */ + public int getLoginTimeout() throws SQLException + { + // TODO Auto-generated method stub + return 0; + } + + /** + * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter) + */ + public void setLogWriter(PrintWriter out) throws SQLException + { + // TODO Auto-generated method stub + + } + + /** + * @see javax.sql.DataSource#setLoginTimeout(int) + */ + public void setLoginTimeout(int seconds) throws SQLException + { + // TODO Auto-generated method stub + + } + + public boolean isWrapperFor(Class iface) throws SQLException + { + // TODO Auto-generated method stub + return false; + } + + public T unwrap(Class iface) throws SQLException + { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockUserTransaction.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockUserTransaction.java new file mode 100644 index 00000000000..4c21e2d637a --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MockUserTransaction.java @@ -0,0 +1,90 @@ +//======================================================================== +//$Id: MockUserTransaction.java 1692 2007-03-23 04:33:07Z janb $ +//Copyright 2006 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 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 + { + // TODO Auto-generated method stub + + } + + /** + * @see javax.transaction.UserTransaction#commit() + */ + public void commit() throws HeuristicMixedException, + HeuristicRollbackException, IllegalStateException, + RollbackException, SecurityException, SystemException + { + // TODO Auto-generated method stub + + } + + /** + * @see javax.transaction.UserTransaction#getStatus() + */ + public int getStatus() throws SystemException + { + // TODO Auto-generated method stub + return 0; + } + + /** + * @see javax.transaction.UserTransaction#rollback() + */ + public void rollback() throws IllegalStateException, SecurityException, + SystemException + { + // TODO Auto-generated method stub + + } + + /** + * @see javax.transaction.UserTransaction#setRollbackOnly() + */ + public void setRollbackOnly() throws IllegalStateException, SystemException + { + // TODO Auto-generated method stub + + } + + /** + * @see javax.transaction.UserTransaction#setTransactionTimeout(int) + */ + public void setTransactionTimeout(int arg0) throws SystemException + { + // TODO Auto-generated method stub + + } + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java new file mode 100644 index 00000000000..a8f35594efb --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java @@ -0,0 +1,122 @@ +//======================================================================== +//Copyright 2010 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 java.util.Collection; + +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.servlet.http.Part; +import javax.servlet.annotation.MultipartConfig; + +import org.eclipse.jetty.util.IO; +/** + * MultiPartTest + * + * Test Servlet 3.0 MultiPart Mime handling. + * + * + */ + +@MultipartConfig(location="foo/bar", maxFileSize=10240, maxRequestSize=-1, fileSizeThreshold=2048) +public class MultiPartTest extends HttpServlet +{ + private ServletConfig config; + + + public void init(ServletConfig config) throws ServletException + { + super.init(config); + this.config = config; + } + + + + /* ------------------------------------------------------------ */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + + try + { + response.setContentType("text/html"); + ServletOutputStream out = response.getOutputStream(); + out.println(""); + out.println("

    Results

    "); + out.println(""); + out.println("

    "); + + Collection parts = request.getParts(); + out.println("Parts: "+parts.size()); + for (Part p: parts) + { + out.println("

    "+p.getName()+"

    "); + out.println("Size: "+p.getSize()); + if (p.getContentType() == null || p.getContentType().startsWith("text/plain")) + { + out.println("

    "); + IO.copy(p.getInputStream(),out); + out.println("

    "); + } + } + out.println(""); + out.println(""); + out.flush(); + } + catch (ServletException e) + { + throw e; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + /* ------------------------------------------------------------ */ + 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("

    Use a POST Instead

    "); + out.println(""); + out.println(""); + out.flush(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + + + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java new file mode 100644 index 00000000000..34ef9389430 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java @@ -0,0 +1,93 @@ +//======================================================================== +//Copyright 2010 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 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.annotation.security.DeclareRoles; + +/** + * RoleAnnotationTest + * + * Use DeclareRolesAnnotations from within Jetty. + * + * + */ + + +@DeclareRoles({"server-administrator","user"}) +public class RoleAnnotationTest extends HttpServlet +{ + private ServletConfig _config; + + public void init(ServletConfig config) throws ServletException + { + super.init(config); + _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("

    Jetty DeclareRoles Annotation Results

    "); + out.println(""); + + out.println("

    Roles

    "); + boolean result = request.isUserInRole("other"); + out.println("
    Result: isUserInRole(\"other\")="+result+":"+ (result==false?" PASS":" FAIL")+""); + + result = request.isUserInRole("manager"); + out.println("
    Result: isUserInRole(\"manager\")="+result+":"+ (result?" PASS":" FAIL")+""); + result = request.isUserInRole("user"); + out.println("
    Result: isUserInRole(\"user\")="+result+":"+ (result==false?" PASS":" FAIL")+""); + String context = _config.getServletContext().getContextPath(); + if (!context.endsWith("/")) + context += "/"; + + out.println("

    Logout

    "); + + out.println(""); + out.println(""); + out.flush(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java new file mode 100644 index 00000000000..8479905fe19 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java @@ -0,0 +1,41 @@ +package com.acme; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.annotation.HttpConstraint; +import javax.servlet.annotation.ServletSecurity; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns="/sec/*") +@ServletSecurity(@HttpConstraint(rolesAllowed="admin")) +public class SecuredServlet extends HttpServlet +{ + + + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException + { + PrintWriter writer = resp.getWriter(); + writer.println( ""); + writer.println( ""); + writer.println("

    @ServletSecurity

    "); + writer.println("
    ");
    +        writer.println("@ServletSecurity");
    +        writer.println("public class SecuredServlet");
    +        writer.println("
    "); + writer.println("
    Result: "+true+""); + String context = getServletConfig().getServletContext().getContextPath(); + if (!context.endsWith("/")) + context += "/"; + writer.println("

    Logout

    "); + writer.println( ""); + writer.println( ""); + writer.flush(); + writer.close(); + } +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java new file mode 100644 index 00000000000..ea2d3470dc0 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/TestListener.java @@ -0,0 +1,146 @@ +//======================================================================== +//$Id: TestListener.java 3363 2008-07-22 13:40:59Z janb $ +//Copyright 2004-2005 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 javax.annotation.Resource; +import javax.servlet.annotation.WebListener; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import com.acme.Foo; + + +@Foo(1) +@WebListener +public class TestListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener +{ + + @Resource(mappedName="maxAmount") + private Double maxAmount; + + public void attributeAdded(HttpSessionBindingEvent se) + { + // System.err.println("attributedAdded "+se); + } + + public void attributeRemoved(HttpSessionBindingEvent se) + { + // System.err.println("attributeRemoved "+se); + } + + public void attributeReplaced(HttpSessionBindingEvent se) + { + // System.err.println("attributeReplaced "+se); + } + + public void sessionWillPassivate(HttpSessionEvent se) + { + // System.err.println("sessionWillPassivate "+se); + } + + public void sessionDidActivate(HttpSessionEvent se) + { + // System.err.println("sessionDidActivate "+se); + } + + public void contextInitialized(ServletContextEvent sce) + { + //System.err.println("contextInitialized, maxAmount injected as "+maxAmount); + } + + public void contextDestroyed(ServletContextEvent sce) + { + // System.err.println("contextDestroyed "+sce); + } + + public void attributeAdded(ServletContextAttributeEvent scab) + { + // System.err.println("attributeAdded "+scab); + } + + public void attributeRemoved(ServletContextAttributeEvent scab) + { + // System.err.println("attributeRemoved "+scab); + } + + public void attributeReplaced(ServletContextAttributeEvent scab) + { + // System.err.println("attributeReplaced "+scab); + } + + public void requestDestroyed(ServletRequestEvent sre) + { + // System.err.println("requestDestroyed "+sre); + } + + public void requestInitialized(ServletRequestEvent sre) + { + // System.err.println("requestInitialized "+sre); + } + + public void attributeAdded(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeAdded "+srae); + } + + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeRemoved "+srae); + } + + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + // System.err.println("attributeReplaced "+srae); + } + + public void sessionCreated(HttpSessionEvent se) + { + // System.err.println("sessionCreated "+se); + } + + public void sessionDestroyed(HttpSessionEvent se) + { + // System.err.println("sessionDestroyed "+se); + } + + public void requestCompleted(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestResumed(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + + public void requestSuspended(ServletRequestEvent rre) + { + // TODO Auto-generated method stub + + } + +} diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml new file mode 100644 index 00000000000..79c365f5095 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + org.eclipse.jetty.webapp.WebInfConfiguration + org.eclipse.jetty.webapp.WebXmlConfiguration + org.eclipse.jetty.webapp.MetaInfConfiguration + org.eclipse.jetty.webapp.FragmentConfiguration + org.eclipse.jetty.plus.webapp.EnvConfiguration + org.eclipse.jetty.plus.webapp.PlusConfiguration + org.eclipse.jetty.annotations.AnnotationConfiguration + org.eclipse.jetty.webapp.JettyWebXmlConfiguration + org.eclipse.jetty.webapp.TagLibConfiguration + + + + /test-annotations + /webapps/test-spec.war + true + + + + + Test Realm + /etc/realm.properties + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml new file mode 100644 index 00000000000..b08559b9e75 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml @@ -0,0 +1,19 @@ + + + + maxAmount + 100 + true + + + + + + jdbc/mydatasource + + + + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml new file mode 100644 index 00000000000..a757cba4c17 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/META-INF/MANIFEST.MF b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..5e9495128c0 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml new file mode 100644 index 00000000000..01d28cd860c --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml @@ -0,0 +1,17 @@ + + + + + + + + + + maxAmount + 55.0 + true + + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..1b2a0df056d --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,8 @@ + + + + + + test-spec webapp is deployed. DO NOT USE IN PRODUCTION! + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..6d894ee6a1e --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,100 @@ + + + + Test Annotations WebApp + + + com.acme.TestListener + + + + + AnnotationTest + 1 + + + + AnnotationTest + /test/* + + + + RoleAnnotationTest + com.acme.RoleAnnotationTest + 1 + + manager + server-administrator + + + + + RoleAnnotationTest + /role/* + + + + Multi + com.acme.MultiPartTest + 2 + + + + Multi + /multi/* + + + + com.acme.AnnotationTest/avgAmount + 1.25 + java.lang.Double + + + + someAmount + 0.99 + java.lang.Double + + + + + Admin Role + /role/* + + + admin + + + + + admin + + + + + + + FORM + Test Realm + + + /login.html + + + /authfail.html + + + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html new file mode 100644 index 00000000000..17dc9670a56 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html @@ -0,0 +1,6 @@ + + Authentication Failure</title</head> + <body> + <h1>Authentication Failure</h1> + <p>Sorry, either your login or password were incorrect, please try again.</p> +</html> diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/jetty_banner.gif b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/jetty_banner.gif new file mode 100644 index 00000000000..0a9b1e019ba Binary files /dev/null and b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/jetty_banner.gif differ diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif new file mode 100644 index 00000000000..c5dd44319f0 Binary files /dev/null and b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif differ diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html new file mode 100644 index 00000000000..b2ba7cd6efa --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html @@ -0,0 +1,70 @@ +<HTML> + <HEAD> + <TITLE>Test Specification WebApp + + + + + + + + +

     

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

    +

    +

      +
    1. 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
      +      
      +
    2. +
    3. Edit your $JETTY_HOME/start.ini file and add the following lines: +
      +      OPTIONS=plus
      +      OPTIONS=annotations
      +      OPTIONS=jta
      +      
      +
    4. +
    + +

    + +

    The Tests

    + +

    Test Servlet 2.5/3.0 Annotations, Fragments and Initializers

    +
    + +
    + +

    Test Servlet 3.0 Multipart Mime

    +
    + File to upload: + +
    + + +
    +
    + +
    + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html new file mode 100644 index 00000000000..1b44dea40ad --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html @@ -0,0 +1,15 @@ + +Annotation Test + +

    Enter your username and password to login

    + Enter login=admin and password=admin in order to authenticate successfully +
    + Login: +

    + Password: +

    + +

    +

    + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp new file mode 100644 index 00000000000..0a001c3745f --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp @@ -0,0 +1,21 @@ +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ page import="java.util.*"%> +<%@ page import="javax.servlet.*" %> +<%@ page import="javax.servlet.http.*" %> + + +Logout + + + + <% + HttpSession s = request.getSession(false); + s.invalidate(); + %> +

    Logout

    + +

    You are now logged out.

    + Home + + + diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml new file mode 100644 index 00000000000..8eacd850837 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml @@ -0,0 +1,43 @@ + + + + + + + + + + maxAmount + 55.0 + true + + + + + + + + + + + + + + + + + + + + + + + jdbc/mydatasource + + + + + + + + diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml new file mode 100644 index 00000000000..f28b2443579 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + + org.eclipse.jetty.tests + test-servlet-spec-parent + 9.0.0-SNAPSHOT + + Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar + org.eclipse.jetty.tests + test-web-fragment + jar + + + + maven-compiler-plugin + + 1.6 + 1.6 + false + + + + + + + org.eclipse.jetty.orbit + javax.servlet + + + diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/FragmentServlet.java b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/FragmentServlet.java new file mode 100644 index 00000000000..498197741ec --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/FragmentServlet.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 java.io.IOException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +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; + +/** + * FragmentServlet + * + * A web fragment jar. + */ + +public class FragmentServlet extends HttpServlet +{ + private ServletConfig config; + + + 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("

    Jetty Fragment Servlet

    "); + out.println(""); + out.println(""); + out.println(""); + out.flush(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + + + +} diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html new file mode 100644 index 00000000000..0b686ef9276 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html @@ -0,0 +1,8 @@ +

    Welcome to a Fragment

    + +

    +This index.html file was included in a fragment's META-INF/resources directory. +

    + +Now hit a servlet added by a fragment + diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml new file mode 100644 index 00000000000..c4b334a1469 --- /dev/null +++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml @@ -0,0 +1,39 @@ + + + + + FragmentA + + + + + + + AnnotationTest + com.acme.AnnotationTest + + extra1123 + + + extra2345 + + + + + Fragment + com.acme.FragmentServlet + + + + Fragment + /fragment/* + + + + + +