diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000000..80b6f4b7806
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: java
+jdk:
+ - openjdk7
+ - oraclejdk7
diff --git a/README.txt b/README.txt
index 9157b3eaa56..12c781192c9 100644
--- a/README.txt
+++ b/README.txt
@@ -1,5 +1,6 @@
This is a source checkout of the Jetty webserver.
+
To build, use:
mvn clean install
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
index ec27c98fed0..b30373e310c 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
@@ -150,11 +150,11 @@ public class LikeJettyXml
// === jetty-requestlog.xml ===
NCSARequestLog requestLog = new NCSARequestLog();
- requestLog.setFilename(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
+ requestLog.setFilename(jetty_home + "/logs/yyyy_mm_dd.request.log");
requestLog.setFilenameDateFormat("yyyy_MM_dd");
requestLog.setRetainDays(90);
requestLog.setAppend(true);
- requestLog.setExtended(false);
+ requestLog.setExtended(true);
requestLog.setLogCookies(false);
requestLog.setLogTimeZone("GMT");
RequestLogHandler requestLogHandler = new RequestLogHandler();
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
index 8171a947e28..c2aa948dbb7 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
@@ -25,6 +25,7 @@ import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
+import org.eclipse.jetty.server.AsyncNCSARequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -152,7 +153,8 @@ public class SpdyServer
login.setConfig(jetty_home + "/etc/realm.properties");
server.addBean(login);
- NCSARequestLog requestLog = new NCSARequestLog(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
+ NCSARequestLog requestLog = new AsyncNCSARequestLog();
+ requestLog.setFilename(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
requestLog.setExtended(false);
requestLogHandler.setRequestLog(requestLog);
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
index 2f79754951e..d7290cf4170 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
@@ -326,10 +326,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
//Convert from Resource to URI
ArrayList containerUris = new ArrayList();
- for (Resource r : context.getMetaData().getOrderedContainerJars())
+ for (Resource r : context.getMetaData().getContainerResources())
{
URI uri = r.getURI();
- containerUris.add(uri);
+ containerUris.add(uri);
}
parser.parse (containerUris.toArray(new URI[containerUris.size()]),
@@ -457,24 +457,23 @@ public class AnnotationConfiguration extends AbstractConfiguration
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
- parser.parse(classesDir,
- new ClassNameResolver()
- {
- public boolean isExcluded (String name)
- {
- if (context.isSystemClass(name)) return true;
- if (context.isServerClass(name)) return false;
- return false;
- }
+ parser.parseDir(classesDir,
+ 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;
- }
- });
+ 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-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index f49c07ecdb4..daab4099616 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
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.log.Log;
@@ -749,13 +750,14 @@ public class AnnotationParser
* @param resolver
* @throws Exception
*/
- public void parse (Resource dir, ClassNameResolver resolver)
+ public void parseDir (Resource dir, ClassNameResolver resolver)
throws Exception
{
if (!dir.isDirectory() || !dir.exists())
return;
-
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
+
String[] files=dir.list();
for (int f=0;files!=null && f wwwAuthenticates = parseWWWAuthenticate(response);
- if (wwwAuthenticates.isEmpty())
+ HttpHeader header = getAuthenticateHeader();
+ List headerInfos = parseAuthenticateHeader(response, header);
+ if (headerInfos.isEmpty())
{
- LOG.debug("Authentication challenge without WWW-Authenticate header");
- forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
+ LOG.debug("Authentication challenge without {} header", header);
+ forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
return;
}
- final URI uri = request.getURI();
+ URI uri = getAuthenticationURI(request);
Authentication authentication = null;
- WWWAuthenticate wwwAuthenticate = null;
- for (WWWAuthenticate wwwAuthn : wwwAuthenticates)
+ Authentication.HeaderInfo headerInfo = null;
+ for (Authentication.HeaderInfo element : headerInfos)
{
- authentication = client.getAuthenticationStore().findAuthentication(wwwAuthn.type, uri, wwwAuthn.realm);
+ authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
if (authentication != null)
{
- wwwAuthenticate = wwwAuthn;
+ headerInfo = element;
break;
}
}
@@ -117,7 +119,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
}
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
- final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
+ final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
{
@@ -125,7 +127,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
return;
}
- Request newRequest = client.copyRequest(request, uri);
+ Request newRequest = client.copyRequest(request, request.getURI());
authnResult.apply(newRequest);
newRequest.onResponseSuccess(new Response.SuccessListener()
{
@@ -151,37 +153,24 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
}
- private List parseWWWAuthenticate(Response response)
+ private List parseAuthenticateHeader(Response response, HttpHeader header)
{
// TODO: these should be ordered by strength
- List result = new ArrayList<>();
- List values = Collections.list(response.getHeaders().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
+ List result = new ArrayList<>();
+ List values = Collections.list(response.getHeaders().getValues(header.asString()));
for (String value : values)
{
- Matcher matcher = WWW_AUTHENTICATE_PATTERN.matcher(value);
+ Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
if (matcher.matches())
{
String type = matcher.group(1);
String realm = matcher.group(2);
- WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(value, type, realm);
- result.add(wwwAuthenticate);
+ String params = matcher.group(3);
+ Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
+ result.add(headerInfo);
}
}
return result;
}
}
-
- private class WWWAuthenticate
- {
- private final String value;
- private final String type;
- private final String realm;
-
- public WWWAuthenticate(String value, String type, String realm)
- {
- this.value = value;
- this.type = type;
- this.realm = realm;
- }
- }
}
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 a1b542c753f..953855afc9a 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
@@ -40,7 +40,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLEngine;
@@ -61,7 +60,6 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.ssl.SslConnection;
-import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
@@ -209,7 +207,8 @@ public class HttpClient extends ContainerLifeCycle
handlers.add(new ContinueProtocolHandler(this));
handlers.add(new RedirectProtocolHandler(this));
- handlers.add(new AuthenticationProtocolHandler(this));
+ handlers.add(new WWWAuthenticationProtocolHandler(this));
+ handlers.add(new ProxyAuthenticationProtocolHandler(this));
decoderFactories.add(new GZIPContentDecoder.Factory());
@@ -502,8 +501,8 @@ public class HttpClient extends ContainerLifeCycle
channel.configureBlocking(false);
channel.connect(socketAddress);
- Future futureConnection = new ConnectionCallback(destination, promise);
- selectorManager.connect(channel, futureConnection);
+ ConnectionCallback callback = new ConnectionCallback(destination, promise);
+ selectorManager.connect(channel, callback);
}
// Must catch all exceptions, since some like
// UnresolvedAddressException are not IOExceptions.
@@ -971,7 +970,7 @@ public class HttpClient extends ContainerLifeCycle
HttpConnection connection = newHttpConnection(HttpClient.this, appEndPoint, destination);
appEndPoint.setConnection(connection);
- callback.promise.succeeded(connection);
+ callback.succeeded(connection);
return sslConnection;
}
@@ -979,7 +978,7 @@ public class HttpClient extends ContainerLifeCycle
else
{
HttpConnection connection = newHttpConnection(HttpClient.this, endPoint, destination);
- callback.promise.succeeded(connection);
+ callback.succeeded(connection);
return connection;
}
}
@@ -988,11 +987,11 @@ public class HttpClient extends ContainerLifeCycle
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
{
ConnectionCallback callback = (ConnectionCallback)attachment;
- callback.promise.failed(ex);
+ callback.failed(ex);
}
}
- private class ConnectionCallback extends FuturePromise
+ private class ConnectionCallback implements Promise
{
private final HttpDestination destination;
private final Promise promise;
@@ -1002,6 +1001,18 @@ public class HttpClient extends ContainerLifeCycle
this.destination = destination;
this.promise = promise;
}
+
+ @Override
+ public void succeeded(Connection result)
+ {
+ promise.succeeded(result);
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ promise.failed(x);
+ }
}
private class ContentDecoderFactorySet implements Set
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 28a3198d3b3..ad96efbad78 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
@@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
+import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
@@ -251,7 +252,8 @@ public class HttpConnection extends AbstractConnection implements Connection
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
// Authorization
- Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(request.getURI());
+ URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
+ Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(authenticationURI);
if (authnResult != null)
authnResult.apply(request);
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 3bbbaa2ddea..6967fd7e371 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
@@ -18,7 +18,9 @@
package org.eclipse.jetty.client;
+import java.io.Closeable;
import java.io.IOException;
+import java.net.URI;
import java.nio.channels.AsynchronousCloseException;
import java.util.ArrayList;
import java.util.List;
@@ -45,7 +47,7 @@ import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class HttpDestination implements Destination, AutoCloseable, Dumpable
+public class HttpDestination implements Destination, Closeable, Dumpable
{
private static final Logger LOG = Log.getLogger(HttpDestination.class);
@@ -140,6 +142,15 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return proxyAddress != null;
}
+ public URI getProxyURI()
+ {
+ ProxyConfiguration proxyConfiguration = client.getProxyConfiguration();
+ String uri = getScheme() + "://" + proxyConfiguration.getHost();
+ if (!client.isDefaultPort(getScheme(), proxyConfiguration.getPort()))
+ uri += ":" + proxyConfiguration.getPort();
+ return URI.create(uri);
+ }
+
public HttpField getHostField()
{
return hostField;
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 ba579a5b68a..17835baa479 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
@@ -300,8 +300,7 @@ public class HttpSender implements AsyncContentProvider.Listener
}
case SHUTDOWN_OUT:
{
- EndPoint endPoint = connection.getEndPoint();
- endPoint.shutdownOutput();
+ shutdownOutput();
break;
}
case CONTINUE:
@@ -524,6 +523,8 @@ public class HttpSender implements AsyncContentProvider.Listener
break;
}
+ shutdownOutput();
+
exchange.terminateRequest();
HttpDestination destination = connection.getDestination();
@@ -551,6 +552,11 @@ public class HttpSender implements AsyncContentProvider.Listener
return true;
}
+ private void shutdownOutput()
+ {
+ connection.getEndPoint().shutdownOutput();
+ }
+
public boolean abort(Throwable cause)
{
State current = state.get();
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
new file mode 100644
index 00000000000..841c661414d
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.net.URI;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler
+{
+ public ProxyAuthenticationProtocolHandler(HttpClient client)
+ {
+ this(client, DEFAULT_MAX_CONTENT_LENGTH);
+ }
+
+ public ProxyAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+ {
+ super(client, maxContentLength);
+ }
+
+ @Override
+ public boolean accept(Request request, Response response)
+ {
+ return response.getStatus() == HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407;
+ }
+
+ @Override
+ protected HttpHeader getAuthenticateHeader()
+ {
+ return HttpHeader.PROXY_AUTHENTICATE;
+ }
+
+ @Override
+ protected HttpHeader getAuthorizationHeader()
+ {
+ return HttpHeader.PROXY_AUTHORIZATION;
+ }
+
+ @Override
+ protected URI getAuthenticationURI(Request request)
+ {
+ HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
+ return destination.isProxied() ? destination.getProxyURI() : request.getURI();
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java
new file mode 100644
index 00000000000..21c7c0be98f
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.net.URI;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler
+{
+ public WWWAuthenticationProtocolHandler(HttpClient client)
+ {
+ this(client, DEFAULT_MAX_CONTENT_LENGTH);
+ }
+
+ public WWWAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+ {
+ super(client, maxContentLength);
+ }
+
+ @Override
+ public boolean accept(Request request, Response response)
+ {
+ return response.getStatus() == HttpStatus.UNAUTHORIZED_401;
+ }
+
+ @Override
+ protected HttpHeader getAuthenticateHeader()
+ {
+ return HttpHeader.WWW_AUTHENTICATE;
+ }
+
+ @Override
+ protected HttpHeader getAuthorizationHeader()
+ {
+ return HttpHeader.AUTHORIZATION;
+ }
+
+ @Override
+ protected URI getAuthenticationURI(Request request)
+ {
+ return request.getURI();
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java
index aeb7dd4ac89..9341d7b1eb3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java
@@ -20,18 +20,19 @@ package org.eclipse.jetty.client.api;
import java.net.URI;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.Attributes;
/**
* {@link Authentication} represents a mechanism to authenticate requests for protected resources.
*
* {@link Authentication}s are added to an {@link AuthenticationStore}, which is then
- * {@link #matches(String, String, String) queried} to find the right
+ * {@link #matches(String, URI, String) queried} to find the right
* {@link Authentication} mechanism to use based on its type, URI and realm, as returned by
* {@code WWW-Authenticate} response headers.
*
* If an {@link Authentication} mechanism is found, it is then
- * {@link #authenticate(Request, ContentResponse, String, Attributes) executed} for the given request,
+ * {@link #authenticate(Request, ContentResponse, HeaderInfo, Attributes) executed} for the given request,
* returning an {@link Authentication.Result}, which is then stored in the {@link AuthenticationStore}
* so that subsequent requests can be preemptively authenticated.
*/
@@ -56,13 +57,64 @@ public interface Authentication
*
* @param request the request to execute the authentication mechanism for
* @param response the 401 response obtained in the previous attempt to request the protected resource
- * @param wwwAuthenticate the {@code WWW-Authenticate} header chosen for this authentication
- * (among the many that the response may contain)
+ * @param headerInfo the {@code WWW-Authenticate} (or {@code Proxy-Authenticate}) header chosen for this
+ * authentication (among the many that the response may contain)
* @param context the conversation context in case the authentication needs multiple exchanges
* to be completed and information needs to be stored across exchanges
* @return the authentication result, or null if the authentication could not be performed
*/
- Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context);
+ Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context);
+
+ /**
+ * Structure holding information about the {@code WWW-Authenticate} (or {@code Proxy-Authenticate}) header.
+ */
+ public static class HeaderInfo
+ {
+ private final String type;
+ private final String realm;
+ private final String params;
+ private final HttpHeader header;
+
+ public HeaderInfo(String type, String realm, String params, HttpHeader header)
+ {
+ this.type = type;
+ this.realm = realm;
+ this.params = params;
+ this.header = header;
+ }
+
+ /**
+ * @return the authentication type (for example "Basic" or "Digest")
+ */
+ public String getType()
+ {
+ return type;
+ }
+
+ /**
+ * @return the realm name
+ */
+ public String getRealm()
+ {
+ return realm;
+ }
+
+ /**
+ * @return additional authentication parameters
+ */
+ public String getParameters()
+ {
+ return params;
+ }
+
+ /**
+ * @return the {@code Authorization} (or {@code Proxy-Authorization}) header
+ */
+ public HttpHeader getHeader()
+ {
+ return header;
+ }
+ }
/**
* {@link Result} holds the information needed to authenticate a {@link Request} via {@link #apply(Request)}.
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 df001b2030e..4cd716f4f4f 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
@@ -18,6 +18,8 @@
package org.eclipse.jetty.client.api;
+import java.io.Closeable;
+
import org.eclipse.jetty.util.Promise;
/**
@@ -28,7 +30,7 @@ import org.eclipse.jetty.util.Promise;
* may be created by applications that want to do their own connection management via
* {@link Destination#newConnection(Promise)} and {@link Connection#close()}.
*/
-public interface Connection extends AutoCloseable
+public interface Connection extends Closeable
{
/**
* Sends a request with an associated response listener.
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
index bf09e7a88d1..03cece3160a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
@@ -84,5 +84,22 @@ public interface Destination
{
return port;
}
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Address that = (Address)obj;
+ return host.equals(that.host) && port == that.port;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = host.hashCode();
+ result = 31 * result + port;
+ return result;
+ }
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
index 6769e85b23d..7bf9fc29abd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
@@ -71,20 +71,22 @@ public class BasicAuthentication implements Authentication
}
@Override
- public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
+ public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
{
String encoding = StringUtil.__ISO_8859_1;
String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
- return new BasicResult(request.getURI(), value);
+ return new BasicResult(headerInfo.getHeader(), uri, value);
}
private static class BasicResult implements Result
{
+ private final HttpHeader header;
private final URI uri;
private final String value;
- public BasicResult(URI uri, String value)
+ public BasicResult(HttpHeader header, URI uri, String value)
{
+ this.header = header;
this.uri = uri;
this.value = value;
}
@@ -98,8 +100,7 @@ public class BasicAuthentication implements Authentication
@Override
public void apply(Request request)
{
- if (request.getURI().toString().startsWith(uri.toString()))
- request.header(HttpHeader.AUTHORIZATION, value);
+ request.header(header, value);
}
@Override
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
index e168e038392..d17f5664ab5 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.client.util;
+import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;
@@ -77,7 +78,7 @@ import org.eclipse.jetty.client.api.Response;
* }
*
*/
-public class DeferredContentProvider implements AsyncContentProvider, AutoCloseable
+public class DeferredContentProvider implements AsyncContentProvider, Closeable
{
private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
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 61604f62425..b6a54cb870d 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
@@ -85,13 +85,9 @@ public class DigestAuthentication implements Authentication
}
@Override
- public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
+ public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
{
- // Avoid case sensitivity problems on the 'D' character
- String type = "igest";
- wwwAuthenticate = wwwAuthenticate.substring(wwwAuthenticate.indexOf(type) + type.length());
-
- Map params = parseParams(wwwAuthenticate);
+ Map params = parseParameters(headerInfo.getParameters());
String nonce = params.get("nonce");
if (nonce == null || nonce.length() == 0)
return null;
@@ -113,10 +109,10 @@ public class DigestAuthentication implements Authentication
clientQOP = "auth-int";
}
- return new DigestResult(request.getURI(), response.getContent(), realm, user, password, algorithm, nonce, clientQOP, opaque);
+ return new DigestResult(headerInfo.getHeader(), uri, response.getContent(), realm, user, password, algorithm, nonce, clientQOP, opaque);
}
- private Map parseParams(String wwwAuthenticate)
+ private Map parseParameters(String wwwAuthenticate)
{
Map result = new HashMap<>();
List parts = splitParams(wwwAuthenticate);
@@ -154,7 +150,9 @@ public class DigestAuthentication implements Authentication
case ',':
if (quotes % 2 == 0)
{
- result.add(paramString.substring(start, i).trim());
+ String element = paramString.substring(start, i).trim();
+ if (element.length() > 0)
+ result.add(element);
start = i + 1;
}
break;
@@ -181,6 +179,7 @@ public class DigestAuthentication implements Authentication
private class DigestResult implements Result
{
private final AtomicInteger nonceCount = new AtomicInteger();
+ private final HttpHeader header;
private final URI uri;
private final byte[] content;
private final String realm;
@@ -191,8 +190,9 @@ public class DigestAuthentication implements Authentication
private final String qop;
private final String opaque;
- public DigestResult(URI uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
+ public DigestResult(HttpHeader header, URI uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
{
+ this.header = header;
this.uri = uri;
this.content = content;
this.realm = realm;
@@ -213,9 +213,6 @@ public class DigestAuthentication implements Authentication
@Override
public void apply(Request request)
{
- if (!request.getURI().toString().startsWith(uri.toString()))
- return;
-
MessageDigest digester = getMessageDigest(algorithm);
if (digester == null)
return;
@@ -262,7 +259,7 @@ public class DigestAuthentication implements Authentication
}
value.append(", response=\"").append(hashA3).append("\"");
- request.header(HttpHeader.AUTHORIZATION, value.toString());
+ request.header(header, value.toString());
}
private String nextNonceCount()
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
index 3fee5eb7499..eacbb09168b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
@@ -120,6 +120,8 @@ public class InputStreamContentProvider implements ContentProvider
if (failure == null)
{
failure = x;
+ // Signal we have more content to cause a call to
+ // next() which will throw NoSuchElementException.
return true;
}
return false;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
index 5fc20d1971c..42e3b50516f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.util.Arrays;
import java.util.Collection;
+import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
@@ -54,7 +55,7 @@ public abstract class AbstractHttpClientServerTest
public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
{
this.sslContextFactory = sslContextFactory;
- this.scheme = sslContextFactory == null ? "http" : "https";
+ this.scheme = (sslContextFactory == null ? HttpScheme.HTTP : HttpScheme.HTTPS).asString();
}
public void start(Handler handler) throws Exception
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
new file mode 100644
index 00000000000..c645f1cdb3e
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
@@ -0,0 +1,155 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.URI;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.ProxyConfiguration;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientProxyTest extends AbstractHttpClientServerTest
+{
+ public HttpClientProxyTest(SslContextFactory sslContextFactory)
+ {
+ // Avoid TLS otherwise CONNECT requests are sent instead of proxied requests
+ super(null);
+ }
+
+ @Test
+ public void testProxiedRequest() throws Exception
+ {
+ final String serverHost = "server";
+ final int status = HttpStatus.NO_CONTENT_204;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ if (serverHost.equals(request.getServerName()))
+ response.setStatus(status);
+ }
+ });
+
+ int proxyPort = connector.getLocalPort();
+ int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+ client.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
+
+ ContentResponse response = client.newRequest(serverHost, serverPort)
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(status, response.getStatus());
+ }
+
+ @Test
+ public void testAuthenticatedProxiedRequest() throws Exception
+ {
+ final String user = "foo";
+ final String password = "bar";
+ final String credentials = B64Code.encode(user + ":" + password, "ISO-8859-1");
+ final String serverHost = "server";
+ final String realm = "test_realm";
+ final int status = HttpStatus.NO_CONTENT_204;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
+ if (authorization == null)
+ {
+ response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
+ response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
+ }
+ else
+ {
+ String prefix = "Basic ";
+ if (authorization.startsWith(prefix))
+ {
+ String attempt = authorization.substring(prefix.length());
+ if (credentials.equals(attempt))
+ response.setStatus(status);
+ }
+ }
+ }
+ });
+
+ String proxyHost = "localhost";
+ int proxyPort = connector.getLocalPort();
+ int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+ client.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
+
+ ContentResponse response1 = client.newRequest(serverHost, serverPort)
+ .scheme(scheme)
+ .timeout(555, TimeUnit.SECONDS)
+ .send();
+
+ // No Authentication available => 407
+ Assert.assertEquals(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, response1.getStatus());
+
+ // Add authentication...
+ URI uri = URI.create(scheme + "://" + proxyHost + ":" + proxyPort);
+ client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, user, password));
+ final AtomicInteger requests = new AtomicInteger();
+ client.getRequestListeners().add(new Request.Listener.Empty()
+ {
+ @Override
+ public void onSuccess(Request request)
+ {
+ requests.incrementAndGet();
+ }
+ });
+ // ...and perform the request again => 407 + 204
+ ContentResponse response2 = client.newRequest(serverHost, serverPort)
+ .scheme(scheme)
+ .timeout(555, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(status, response2.getStatus());
+ Assert.assertEquals(2, requests.get());
+
+ // Now the authentication result is cached => 204
+ requests.set(0);
+ ContentResponse response3 = client.newRequest(serverHost, serverPort)
+ .scheme(scheme)
+ .timeout(555, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertEquals(status, response3.getStatus());
+ Assert.assertEquals(1, requests.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 a960e865ad6..218f0e090aa 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
@@ -32,6 +32,7 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -48,6 +49,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.OutputStreamContentProvider;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -443,6 +445,37 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
Assert.assertNull(failure.get());
}
+ @Test(expected = ExecutionException.class)
+ public void testInputStreamContentProviderThrowingWhileReading() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ IO.copy(request.getInputStream(), response.getOutputStream());
+ }
+ });
+
+ final byte[] data = new byte[]{0, 1, 2, 3};
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .content(new InputStreamContentProvider(new InputStream()
+ {
+ private int index = 0;
+
+ @Override
+ public int read() throws IOException
+ {
+ // Will eventually throw ArrayIndexOutOfBounds
+ return data[index++];
+ }
+ }, data.length / 2))
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+ }
+
@Test(expected = AsynchronousCloseException.class)
public void testDownloadWithCloseBeforeContent() throws Exception
{
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 0f449f04eef..3f9ba0a3a37 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -219,7 +219,25 @@
${assembly-directory}/lib/websocket
-
+
+ copy-lib-monitor-deps
+ generate-resources
+
+ copy
+
+
+
+
+ org.eclipse.jetty
+ jetty-monitor
+ ${project.version}
+ jar
+ true
+ ${assembly-directory}/lib/monitor
+
+
+
+
copy-orbit-servlet-api-deps
generate-resources
@@ -421,6 +439,11 @@
jetty-jmx
${project.version}
+
+ org.eclipse.jetty
+ jetty-monitor
+ ${project.version}
+
org.eclipse.jetty
jetty-start
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index 17ba7055368..cc3f771aa1e 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -52,7 +52,7 @@
#
# /webapps/jetty.war
#
-# JETTY_PORT
+# JETTY_PORT (Deprecated - use JETTY_ARGS)
# Override the default port for Jetty servers. If not set then the
# default value in the xml configuration file will be used. The java
# system property "jetty.port" will be set to this value for use in
@@ -76,6 +76,8 @@
#
# JETTY_ARGS
# The default arguments to pass to jetty.
+# For example
+# JETTY_ARGS=jetty.port=8080 jetty.spdy.port=8443 jetty.secure.port=443
#
# JETTY_USER
# if set, then used as a username to run the server as
@@ -106,7 +108,7 @@ findDirectory()
running()
{
- local PID=$(head -n 1 "$1" 2>/dev/null) || return 1
+ local PID=$(cat "$1" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null
}
@@ -119,7 +121,7 @@ started()
[ -z "$(grep STARTED $1)" ] || return 0
[ -z "$(grep STOPPED $1)" ] || return 1
[ -z "$(grep FAILED $1)" ] || return 1
- local PID=$(head -n 1 "$1" 2>/dev/null) || return 1
+ local PID=$(cat "$2" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null || return 1
echo -n ". "
done
@@ -342,7 +344,9 @@ if [ -z "$JETTY_PID" ]
then
JETTY_PID="$JETTY_RUN/jetty.pid"
fi
-JAVA_OPTIONS+=("-Djetty.pid=$JETTY_PID")
+JETTY_STATE=$(dirname $JETTY_PID)/jetty.state
+JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
+rm -f $JETTY_STATE
##################################################
# Setup JAVA if unset
@@ -473,11 +477,16 @@ case "$ACTION" in
fi
- if started "$JETTY_PID"
+ if expr "${CONFIGS[*]}" : '.*etc/jetty-started.xml.*' >/dev/null
then
- echo "OK `date`"
+ if started "$JETTY_STATE" "$JETTY_PID"
+ then
+ echo "OK `date`"
+ else
+ echo "FAILED `date`"
+ fi
else
- echo "FAILED `date`"
+ echo "ok `date`"
fi
;;
@@ -499,7 +508,7 @@ case "$ACTION" in
rm -f "$JETTY_PID"
echo OK
else
- PID=$(head -n 1 "$JETTY_PID" 2>/dev/null)
+ PID=$(cat "$JETTY_PID" 2>/dev/null)
kill "$PID" 2>/dev/null
TIMEOUT=30
diff --git a/jetty-distribution/src/main/resources/etc/jetty-started.xml b/jetty-distribution/src/main/resources/etc/jetty-started.xml
index 15d4db3c5bb..9207b1c9b41 100644
--- a/jetty-distribution/src/main/resources/etc/jetty-started.xml
+++ b/jetty-distribution/src/main/resources/etc/jetty-started.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index ee6b4f7f844..382010d19b3 100644
--- a/jetty-distribution/src/main/resources/start.ini
+++ b/jetty-distribution/src/main/resources/start.ini
@@ -55,10 +55,6 @@
# jetty.home=.
# jetty.logs=./logs
# jetty.host=0.0.0.0
-# jetty.port=8080
-# jetty.tls.port=8443
-# jetty.jmxrmihost=localhost
-# jetty.jmxrmiport=1099
#===========================================================
@@ -89,11 +85,9 @@
# Add the contents of the resources directory to the classpath
# Add jars discovered in lib/ext to the classpath
# Include the core jetty configuration file
-# Lookup additional ini files in start.d
#-----------------------------------------------------------
OPTIONS=Server,websocket,resources,ext
etc/jetty.xml
-start.d/
#===========================================================
#===========================================================
@@ -123,6 +117,8 @@ start.d/
# enable --exec or use --exec-print (see above)
#-----------------------------------------------------------
OPTIONS=jmx
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
# -Dcom.sun.management.jmxremote
etc/jetty-jmx.xml
#===========================================================
@@ -146,38 +142,51 @@ OPTIONS=jsp
#===========================================================
# HTTP Connector
#-----------------------------------------------------------
+# jetty.port=8080
etc/jetty-http.xml
#===========================================================
+#===========================================================
+# SSL Context
+# For use by HTTPS and SPDY
+#-----------------------------------------------------------
+# jetty.keystore=etc/keystore
+# jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+# jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+# jetty.truststore=etc/keystore
+# jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+# jetty.secure.port=8443
+# etc/jetty-ssl.xml
+#===========================================================
+
#===========================================================
# HTTPS Connector
#-----------------------------------------------------------
+# jetty.https.port=8443
# etc/jetty-https.xml
#===========================================================
#===========================================================
# SPDY Connector
#
-# SPDY should not be used with HTTPS. Use HTTPS or SPDY, but
-# not both, as SPDY also provides HTTPS support.
+# SPDY requires the NPN jar which must be separately downloaded:
#
-# SPDY requires the NPN jar iwhich must be separately downloaded:
+# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar
#
-# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.2.v20130305/npn-boot-1.1.2.v20130305.jar
-#
-# Which should be saved in lib/npn-boot-1.1.2.v20130305.jar
+# Which should be saved in lib/npn-boot-1.1.5.v20130313.jar
#
# To include the NPN jar on the boot path, you must either:
#
# a) enable --exec above and uncomment the -Xbootclass line
# below
#
-# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.2.v20130305.jar
+# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
# to the command line when running jetty.
#
#-----------------------------------------------------------
# OPTIONS=spdy
-# -Xbootclasspath/p:lib/npn-boot-1.1.2.v20130305.jar
+# -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
+# jetty.spdy.port=8443
# etc/jetty-spdy.xml
#===========================================================
@@ -204,3 +213,9 @@ etc/jetty-requestlog.xml
# etc/jetty-ipaccess.xml
# etc/jetty-lowresources.xml
#===========================================================
+
+#===========================================================
+# Lookup additional ini files in start.d
+#-----------------------------------------------------------
+start.d/
+#===========================================================
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 f59548589d7..67c95111e16 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
@@ -39,12 +39,14 @@ import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -1007,7 +1009,7 @@ public class HttpFields implements Iterable
private static final Float __one = new Float("1.0");
private static final Float __zero = new Float("0.0");
- private static final StringMap __qualities = new StringMap<>();
+ private static final Trie __qualities = new ArrayTernaryTrie<>();
static
{
__qualities.put("*", __one);
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
index d8e25434d36..0385c8ed663 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
@@ -22,11 +22,11 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.StringTokenizer;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.LazyList;
-import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.URIUtil;
/* ------------------------------------------------------------ */
@@ -78,14 +78,13 @@ public class PathMap extends HashMap
}
/* --------------------------------------------------------------- */
- final StringMap> _prefixMap=new StringMap<>();
- final StringMap> _suffixMap=new StringMap<>();
- final StringMap> _exactMap=new StringMap<>();
+ Trie> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map> _exactMap=new HashMap<>();
- List _defaultSingletonList=null;
+ List> _defaultSingletonList=null;
MappedEntry _prefixDefault=null;
MappedEntry _default=null;
- final Set _entrySet;
boolean _nodefault=false;
/* --------------------------------------------------------------- */
@@ -111,7 +110,6 @@ public class PathMap extends HashMap
{
super(capacity);
_nodefault=noDefault;
- _entrySet=entrySet();
}
/* --------------------------------------------------------------- */
@@ -120,7 +118,6 @@ public class PathMap extends HashMap
public PathMap(Map m)
{
putAll(m);
- _entrySet=entrySet();
}
/* --------------------------------------------------------------- */
@@ -163,12 +160,15 @@ public class PathMap extends HashMap
{
String mapped=spec.substring(0,spec.length()-2);
entry.setMapped(mapped);
- _prefixMap.put(mapped,entry);
- _exactMap.put(mapped,entry);
- _exactMap.put(spec.substring(0,spec.length()-1),entry);
+ while (!_prefixMap.put(mapped,entry))
+ _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_prefixMap,1.5);
}
else if (spec.startsWith("*."))
- _suffixMap.put(spec.substring(2),entry);
+ {
+ String suffix=spec.substring(2);
+ while(!_suffixMap.put(suffix,entry))
+ _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_suffixMap,1.5);
+ }
else if (spec.equals(URIUtil.SLASH))
{
if (_nodefault)
@@ -176,8 +176,7 @@ public class PathMap extends HashMap
else
{
_default=entry;
- _defaultSingletonList=
- Collections.singletonList(_default);
+ _defaultSingletonList=Collections.singletonList(_default);
}
}
else
@@ -228,17 +227,22 @@ public class PathMap extends HashMap
}
// try exact match
- entry=_exactMap.get(path,0,l);
+ entry=_exactMap.get(path);
if (entry!=null)
return entry;
// prefix search
int i=l;
- while((i=path.lastIndexOf('/',i-1))>=0)
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
{
- entry=_prefixMap.get(path,0,i);
- if (entry!=null)
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
return entry;
+ i=key.length()-3;
}
// Prefix Default
@@ -247,9 +251,10 @@ public class PathMap extends HashMap
// Extension search
i=0;
+ final Trie> suffix_map=_suffixMap;
while ((i=path.indexOf('.',i+1))>0)
{
- entry=_suffixMap.get(path,i+1,l-i-1);
+ entry=suffix_map.get(path,i+1,l-i-1);
if (entry!=null)
return entry;
}
@@ -266,26 +271,31 @@ public class PathMap extends HashMap
*/
public Object getLazyMatches(String path)
{
- MappedEntry entry;
+ MappedEntry entry;
Object entries=null;
if (path==null)
return LazyList.getList(entries);
- int l=path.length();
-
// try exact match
- entry=_exactMap.get(path,0,l);
+ entry=_exactMap.get(path);
if (entry!=null)
entries=LazyList.add(entries,entry);
// prefix search
- int i=l-1;
- while((i=path.lastIndexOf('/',i-1))>=0)
+ int l=path.length();
+ int i=l;
+ final Trie> prefix_map=_prefixMap;
+ while(i>=0)
{
- entry=_prefixMap.get(path,0,i);
- if (entry!=null)
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
entries=LazyList.add(entries,entry);
+
+ i=key.length()-3;
}
// Prefix Default
@@ -294,9 +304,10 @@ public class PathMap extends HashMap
// Extension search
i=0;
+ final Trie> suffix_map=_suffixMap;
while ((i=path.indexOf('.',i+1))>0)
{
- entry=_suffixMap.get(path,i+1,l-i-1);
+ entry=suffix_map.get(path,i+1,l-i-1);
if (entry!=null)
entries=LazyList.add(entries,entry);
}
@@ -320,7 +331,7 @@ public class PathMap extends HashMap
* @param path Path to match
* @return List of Map.Entry instances key=pathSpec
*/
- public List getMatches(String path)
+ public List> getMatches(String path)
{
return LazyList.getList(getLazyMatches(path));
}
@@ -333,7 +344,7 @@ public class PathMap extends HashMap
*/
public boolean containsMatch(String path)
{
- MappedEntry match = getMatch(path);
+ MappedEntry> match = getMatch(path);
return match!=null && !match.equals(_default);
}
@@ -347,11 +358,7 @@ public class PathMap extends HashMap
if (spec.equals("/*"))
_prefixDefault=null;
else if (spec.endsWith("/*"))
- {
_prefixMap.remove(spec.substring(0,spec.length()-2));
- _exactMap.remove(spec.substring(0,spec.length()-1));
- _exactMap.remove(spec.substring(0,spec.length()-2));
- }
else if (spec.startsWith("*."))
_suffixMap.remove(spec.substring(2));
else if (spec.equals(URIUtil.SLASH))
@@ -370,8 +377,8 @@ public class PathMap extends HashMap
public void clear()
{
_exactMap.clear();
- _prefixMap.clear();
- _suffixMap.clear();
+ _prefixMap=new ArrayTernaryTrie<>(false);
+ _suffixMap=new ArrayTernaryTrie<>(false);
_default=null;
_defaultSingletonList=null;
super.clear();
@@ -382,18 +389,18 @@ public class PathMap extends HashMap
* @return true if match.
*/
public static boolean match(String pathSpec, String path)
- throws IllegalArgumentException
- {
+ throws IllegalArgumentException
+ {
return match(pathSpec, path, false);
- }
+ }
/* --------------------------------------------------------------- */
/**
* @return true if match.
*/
public static boolean match(String pathSpec, String path, boolean noDefault)
- throws IllegalArgumentException
- {
+ throws IllegalArgumentException
+ {
char c = pathSpec.charAt(0);
if (c=='/')
{
@@ -407,7 +414,7 @@ public class PathMap extends HashMap
return path.regionMatches(path.length()-pathSpec.length()+1,
pathSpec,1,pathSpec.length()-1);
return false;
- }
+ }
/* --------------------------------------------------------------- */
private static boolean isPathWildcardMatch(String pathSpec, String path)
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
index cda9f11e63b..36e9b2e1581 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
@@ -44,6 +44,7 @@ public class PathMapTest
p.put("/", "8");
p.put("/XXX:/YYY", "9");
p.put("", "10");
+ p.put("/\u20ACuro/*", "11");
String[][] tests = {
{ "/abs/path", "1"},
@@ -62,7 +63,9 @@ public class PathMapTest
{ "/suffix/path.tar.gz", "6"},
{ "/suffix/path.gz", "7"},
{ "/animal/path.gz", "5"},
- { "/Other/path", "8"},};
+ { "/Other/path", "8"},
+ { "/\u20ACuro/path", "11"},
+ };
for (String[] test : tests)
{
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
index 41b031fb014..1a1f7f4221b 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -41,6 +41,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -93,7 +94,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
/**
* Get the connect timeout
- *
+ *
* @return the connect timeout (in milliseconds)
*/
public long getConnectTimeout()
@@ -103,7 +104,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
/**
* Set the connect timeout (in milliseconds)
- *
+ *
* @param milliseconds the number of milliseconds for the timeout
*/
public void setConnectTimeout(long milliseconds)
@@ -317,7 +318,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
*/
public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
{
- private final Queue _changes = new ConcurrentLinkedQueue<>();
+ private final Queue _changes = new ConcurrentArrayQueue<>();
+
private final int _id;
private Selector _selector;
private volatile Thread _thread;
@@ -364,7 +366,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
if (_runningChanges)
_changes.offer(change);
else
- {
+ {
// Otherwise we run the queued changes
runChanges();
// and then directly run the passed change
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 9ced4857d24..9d13a1a8d0b 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -538,8 +538,10 @@ public class SslConnection extends AbstractConnection
case NOT_HANDSHAKING:
// we just didn't read anything.
if (net_filled < 0)
- _sslEngine.closeInbound();
-
+ {
+ closeInbound();
+ return -1;
+ }
return 0;
case NEED_TASK:
@@ -568,14 +570,8 @@ public class SslConnection extends AbstractConnection
// if we just filled some net data
if (net_filled < 0)
{
- // If we call closeInbound() before having read the SSL close
- // message an exception will be thrown (truncation attack).
- // The TLS specification says that the sender of the SSL close
- // message may just close and avoid to read the response.
- // If that is the case, we avoid calling closeInbound() because
- // will throw the truncation attack exception for nothing.
- if (isOpen())
- _sslEngine.closeInbound();
+ closeInbound();
+ return -1;
}
else if (net_filled > 0)
{
@@ -625,6 +621,18 @@ public class SslConnection extends AbstractConnection
}
}
+ private void closeInbound()
+ {
+ try
+ {
+ _sslEngine.closeInbound();
+ }
+ catch (SSLException x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
@Override
public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
{
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
index c1c8e4215d5..6f956a18e4b 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
@@ -26,6 +26,7 @@ import java.nio.channels.SocketChannel;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.io.ssl.SslConnection;
@@ -197,10 +198,28 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
Assert.assertEquals("HelloWorld",reply);
+ if (debug) System.err.println("Shutting down output");
client.socket().shutdownOutput();
+ filled=client.read(sslIn);
+ if (debug) System.err.println("in="+filled);
+ sslIn.flip();
+ try
+ {
+ // Since the client closed abruptly, the server is sending a close alert with a failure
+ engine.unwrap(sslIn, appIn);
+ Assert.fail();
+ }
+ catch (SSLException x)
+ {
+ // Expected
+ }
+
+ sslIn.clear();
filled=client.read(sslIn);
Assert.assertEquals(-1,filled);
+
+ Assert.assertFalse(server.isOpen());
}
@Test
diff --git a/jetty-jsp/pom.xml b/jetty-jsp/pom.xml
index 3ba7dd900ae..aa406a55a6e 100644
--- a/jetty-jsp/pom.xml
+++ b/jetty-jsp/pom.xml
@@ -64,7 +64,7 @@
org.eclipse.jetty.orbit
javax.el
- 2.2.0.v201108011116
+ 2.2.0.v201303151357
org.eclipse.jetty.orbit
@@ -76,7 +76,7 @@
org.eclipse.jetty.orbit
com.sun.el
- 2.2.0.v201108011116
+ 2.2.0.v201303151357
org.eclipse.jetty.orbit
@@ -88,7 +88,7 @@
org.eclipse.jetty.orbit
org.eclipse.jdt.core
- 3.7.1
+ 3.8.2.v20130121
org.eclipse.jetty.orbit
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
index 08b6f5c96f8..e9c2382a283 100644
--- 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
@@ -88,7 +88,7 @@ public class MavenAnnotationConfiguration extends AnnotationConfiguration
public void doParse (final WebAppContext context, final AnnotationParser parser, Resource resource)
throws Exception
{
- parser.parse(resource, new ClassNameResolver()
+ parser.parseDir(resource, new ClassNameResolver()
{
public boolean isExcluded (String name)
{
diff --git a/jetty-monitor/README.txt b/jetty-monitor/README.txt
new file mode 100644
index 00000000000..72c7da114f9
--- /dev/null
+++ b/jetty-monitor/README.txt
@@ -0,0 +1,13 @@
+The ThreadMonitor is distributed as part of the jetty-monitor module.
+
+In order to start ThreadMonitor when server starts up, the following command line should be used.
+
+ java -jar start.jar OPTIONS=monitor jetty-monitor.xml
+
+To run ThreadMonitor on a Jetty installation that doesn't include jetty-monitor module, the jetty-monitor-[version].jar file needs to be copied into ${jetty.home}/lib/ext directory, and jetty-monitor.xml configuration file needs to be copied into ${jetty.home}/etc directory. Subsequently, the following command line should be used.
+
+ java -jar start.jar etc/jetty-monitor.xml
+
+If running Jetty on Java VM version 1.5, the -Dcom.sun.management.jmxremote option should be added to the command lines above in order to enable the JMX agent.
+
+In order to log CPU utilization for threads that are above specified threshold, you need to follow instructions inside jetty-monitor.xml configuration file.
\ No newline at end of file
diff --git a/jetty-monitor/pom.xml b/jetty-monitor/pom.xml
new file mode 100644
index 00000000000..ee63fa2a0b7
--- /dev/null
+++ b/jetty-monitor/pom.xml
@@ -0,0 +1,145 @@
+
+
+
+ org.eclipse.jetty
+ jetty-project
+ 9.0.1-SNAPSHOT
+
+ 4.0.0
+ jetty-monitor
+ Jetty :: Monitoring
+ Performance monitoring artifact for jetty.
+
+ ${project.groupId}.monitor
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ manifest
+
+
+
+ javax.management.*,*
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ package
+
+ single
+
+
+
+ config
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ always
+
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
+ org.eclipse.jetty.monitor.*
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-http
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-xml
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-client
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-jmx
+ ${project.version}
+ test
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ test
+
+
+
diff --git a/jetty-monitor/src/main/config/etc/jetty-monitor.xml b/jetty-monitor/src/main/config/etc/jetty-monitor.xml
new file mode 100644
index 00000000000..6a866dda28c
--- /dev/null
+++ b/jetty-monitor/src/main/config/etc/jetty-monitor.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ 2000
+ 90
+ 3
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/JMXMonitor.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/JMXMonitor.java
new file mode 100644
index 00000000000..253a35240ed
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/JMXMonitor.java
@@ -0,0 +1,194 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.monitor;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.management.MBeanServerConnection;
+
+import org.eclipse.jetty.monitor.jmx.MonitorAction;
+import org.eclipse.jetty.monitor.jmx.MonitorTask;
+import org.eclipse.jetty.monitor.jmx.ServiceConnection;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/* ------------------------------------------------------------ */
+/**
+ * JMXMonitor
+ *
+ * Performs monitoring of the values of the attributes of MBeans
+ * and executes specified actions as well as sends notifications
+ * of the specified events that have occurred.
+ */
+public class JMXMonitor
+{
+ private static JMXMonitor __monitor = new JMXMonitor();
+
+ private String _serverUrl;
+ private ServiceConnection _serviceConnection;
+
+ private Set _actions = new HashSet();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructs a JMXMonitor instance. Used for XML Configuration.
+ *
+ * !! DO NOT INSTANTIATE EXPLICITLY !!
+ */
+ public JMXMonitor() {}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Adds monitor actions to the monitor
+ *
+ * @param actions monitor actions to add
+ * @return true if successful
+ */
+ public boolean addActions(MonitorAction... actions)
+ {
+ return getInstance().add(actions);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes monitor actions from the monitor
+ *
+ * @param actions monitor actions to remove
+ * @return true if successful
+ */
+ public boolean removeActions(MonitorAction... actions)
+ {
+ return getInstance().remove(actions);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the JMX server URL
+ *
+ * @param url URL of the JMX server
+ */
+ public void setUrl(String url)
+ {
+ getInstance().set(url);
+ }
+
+ public MBeanServerConnection getConnection()
+ throws IOException
+ {
+ return getInstance().get();
+ }
+
+ public static JMXMonitor getInstance()
+ {
+ return __monitor;
+ }
+
+ public static boolean addMonitorActions(MonitorAction... actions)
+ {
+ return getInstance().add(actions);
+ }
+
+ public static boolean removeMonitorActions(MonitorAction... actions)
+ {
+ return getInstance().remove(actions);
+ }
+
+ public static void setServiceUrl(String url)
+ {
+ getInstance().set(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieves a connection to JMX service
+ *
+ * @return server connection
+ * @throws IOException
+ */
+ public static MBeanServerConnection getServiceConnection()
+ throws IOException
+ {
+ return getInstance().getConnection();
+ }
+
+ public static void main(final String args[]) throws Exception
+ {
+ XmlConfiguration.main(args);
+ }
+
+ private synchronized boolean add(MonitorAction... actions)
+ {
+ boolean result = true;
+
+ for (MonitorAction action : actions)
+ {
+ if (!_actions.add(action))
+ {
+ result = false;
+ }
+ else
+ {
+ MonitorTask.schedule(action);
+ }
+ }
+
+ return result;
+ }
+
+ private synchronized boolean remove(MonitorAction... actions)
+ {
+ boolean result = true;
+
+ for (MonitorAction action : actions)
+ {
+ if (!_actions.remove(action))
+ {
+ result = false;
+ }
+
+ MonitorTask.cancel(action);
+ }
+
+ return result;
+ }
+
+ private synchronized void set(String url)
+ {
+ _serverUrl = url;
+
+ if (_serviceConnection != null)
+ {
+ _serviceConnection.disconnect();
+ _serviceConnection = null;
+ }
+ }
+
+ private synchronized MBeanServerConnection get()
+ throws IOException
+ {
+ if (_serviceConnection == null)
+ {
+ _serviceConnection = new ServiceConnection(_serverUrl);
+ _serviceConnection.connect();
+ }
+
+ return _serviceConnection.getConnection();
+ }
+}
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java
new file mode 100644
index 00000000000..1bc74fb32d4
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java
@@ -0,0 +1,592 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.monitor;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.monitor.thread.ThreadMonitorException;
+import org.eclipse.jetty.monitor.thread.ThreadMonitorInfo;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+@ManagedObject("Busy Thread Monitor")
+public class ThreadMonitor extends AbstractLifeCycle implements Runnable
+{
+ private static final Logger LOG = Log.getLogger(ThreadMonitor.class);
+
+ private int _scanInterval;
+ private int _logInterval;
+ private int _busyThreshold;
+ private int _logThreshold;
+ private int _stackDepth;
+ private int _trailLength;
+
+ private ThreadMXBean _threadBean;
+
+ private Thread _runner;
+ private Logger _logger;
+ private volatile boolean _done = true;
+ private Dumpable _dumpable;
+
+ private Map _monitorInfo;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new thread monitor.
+ *
+ * @throws Exception
+ */
+ public ThreadMonitor() throws Exception
+ {
+ this(5000);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new thread monitor.
+ *
+ * @param intervalMs scan interval
+ * @throws Exception
+ */
+ public ThreadMonitor(int intervalMs) throws Exception
+ {
+ this(intervalMs, 95);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new thread monitor.
+ *
+ * @param intervalMs scan interval
+ * @param threshold busy threshold
+ * @throws Exception
+ */
+ public ThreadMonitor(int intervalMs, int threshold) throws Exception
+ {
+ this(intervalMs, threshold, 3);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new thread monitor.
+ *
+ * @param intervalMs scan interval
+ * @param threshold busy threshold
+ * @param depth stack compare depth
+ * @throws Exception
+ */
+ public ThreadMonitor(int intervalMs, int threshold, int depth) throws Exception
+ {
+ this(intervalMs, threshold, depth, 3);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new thread monitor.
+ *
+ * @param intervalMs scan interval
+ * @param threshold busy threshold
+ * @param depth stack compare depth
+ * @param trail length of stack trail
+ * @throws Exception
+ */
+ public ThreadMonitor(int intervalMs, int threshold, int depth, int trail) throws Exception
+ {
+ _scanInterval = intervalMs;
+ _busyThreshold = threshold;
+ _stackDepth = depth;
+ _trailLength = trail;
+
+ _logger = Log.getLogger(ThreadMonitor.class.getName());
+ _monitorInfo = new HashMap();
+
+ init();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the scan interval.
+ *
+ * @return the scan interval
+ */
+ public int getScanInterval()
+ {
+ return _scanInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the scan interval.
+ *
+ * @param ms the new scan interval
+ */
+ public void setScanInterval(int ms)
+ {
+ _scanInterval = ms;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the log interval.
+ *
+ * @return the log interval
+ */
+ public int getLogInterval()
+ {
+ return _logInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the log interval.
+ *
+ * @param ms the new log interval
+ */
+ public void setLogInterval(int ms)
+ {
+ _logInterval = ms;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the busy threshold.
+ *
+ * @return the busy threshold
+ */
+ public int getBusyThreshold()
+ {
+ return _busyThreshold;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the busy threshold.
+ *
+ * @param percent the new busy threshold
+ */
+ public void setBusyThreshold(int percent)
+ {
+ _busyThreshold = percent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the log threshold.
+ *
+ * @return the log threshold
+ */
+ public int getLogThreshold()
+ {
+ return _logThreshold;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the log threshold.
+ *
+ * @param percent the new log threshold
+ */
+ public void setLogThreshold(int percent)
+ {
+ _logThreshold = percent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the stack depth.
+ *
+ * @return the stack depth
+ */
+ public int getStackDepth()
+ {
+ return _stackDepth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the stack depth.
+ *
+ * @param stackDepth the new stack depth
+ */
+ public void setStackDepth(int stackDepth)
+ {
+ _stackDepth = stackDepth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the stack trace trail length.
+ *
+ * @param trailLength the new trail length
+ */
+ public void setTrailLength(int trailLength)
+ {
+ _trailLength = trailLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets the stack trace trail length.
+ *
+ * @return the trail length
+ */
+ public int getTrailLength()
+ {
+ return _trailLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Enable logging of CPU usage.
+ *
+ * @param frequencyMs the logging frequency
+ * @param thresholdPercent the logging threshold
+ */
+ public void logCpuUsage(int frequencyMs, int thresholdPercent)
+ {
+ setLogInterval(frequencyMs);
+ setLogThreshold(thresholdPercent);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A {@link Dumpable} that is dumped whenever spinning threads are detected
+ */
+ public Dumpable getDumpable()
+ {
+ return _dumpable;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param dumpable A {@link Dumpable} that is dumped whenever spinning threads are detected
+ */
+ public void setDumpable(Dumpable dumpable)
+ {
+ _dumpable = dumpable;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ public void doStart()
+ {
+ _done = false;
+
+ _runner = new Thread(this);
+ _runner.setDaemon(true);
+ _runner.start();
+
+ LOG.info("Thread Monitor started successfully");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ public void doStop()
+ {
+ if (_runner != null)
+ {
+ _done = true;
+ try
+ {
+ _runner.join();
+ }
+ catch (InterruptedException ex) {}
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve all avaliable thread ids
+ *
+ * @return array of thread ids
+ */
+ protected long[] getAllThreadIds()
+ {
+ return _threadBean.getAllThreadIds();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the cpu time for specified thread.
+ *
+ * @param id thread id
+ * @return cpu time of the thread
+ */
+ protected long getThreadCpuTime(long id)
+ {
+ return _threadBean.getThreadCpuTime(id);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Initialize JMX objects.
+ */
+ protected void init()
+ {
+ _threadBean = ManagementFactory.getThreadMXBean();
+ if (_threadBean.isThreadCpuTimeSupported())
+ {
+ _threadBean.setThreadCpuTimeEnabled(true);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ // Initialize repeat flag
+ boolean repeat = false;
+ boolean scanNow, logNow;
+
+ // Set next scan time and log time
+ long nextScanTime = System.currentTimeMillis();
+ long nextLogTime = nextScanTime + _logInterval;
+
+ while (!_done)
+ {
+ long currTime = System.currentTimeMillis();
+ scanNow = (currTime > nextScanTime);
+ logNow = (_logInterval > 0 && currTime > nextLogTime);
+ if (repeat || scanNow || logNow)
+ {
+ repeat = collectThreadInfo();
+ logThreadInfo(logNow);
+
+ if (scanNow)
+ {
+ nextScanTime = System.currentTimeMillis() + _scanInterval;
+ }
+ if (logNow)
+ {
+ nextLogTime = System.currentTimeMillis() + _logInterval;
+ }
+ }
+
+ // Sleep only if not going to repeat scanning immediately
+ if (!repeat)
+ {
+ try
+ {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException ex)
+ {
+ LOG.ignore(ex);
+ }
+ }
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Collect thread info.
+ */
+ private boolean collectThreadInfo()
+ {
+ boolean repeat = false;
+ try
+ {
+ // Retrieve stack traces for all threads at once as it
+ // was proven to be an order of magnitude faster when
+ // retrieving a single thread stack trace.
+ Map all = Thread.getAllStackTraces();
+
+ for (Map.Entry entry : all.entrySet())
+ {
+ Thread thread = entry.getKey();
+ long threadId = thread.getId();
+
+ // Skip our own runner thread
+ if (threadId == _runner.getId())
+ {
+ continue;
+ }
+
+ ThreadMonitorInfo currMonitorInfo = _monitorInfo.get(Long.valueOf(threadId));
+ if (currMonitorInfo == null)
+ {
+ // Create thread info object for a new thread
+ currMonitorInfo = new ThreadMonitorInfo(thread);
+ currMonitorInfo.setStackTrace(entry.getValue());
+ currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
+ currMonitorInfo.setSampleTime(System.nanoTime());
+ _monitorInfo.put(Long.valueOf(threadId), currMonitorInfo);
+ }
+ else
+ {
+ // Update the existing thread info object
+ currMonitorInfo.setStackTrace(entry.getValue());
+ currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
+ currMonitorInfo.setSampleTime(System.nanoTime());
+
+ // Stack trace count holds logging state
+ int count = currMonitorInfo.getTraceCount();
+ if (count >= 0 && currMonitorInfo.isSpinning())
+ {
+ // Thread was spinning and was logged before
+ if (count < _trailLength)
+ {
+ // Log another stack trace
+ currMonitorInfo.setTraceCount(count+1);
+ repeat = true;
+ continue;
+ }
+
+ // Reset spin flag and trace count
+ currMonitorInfo.setSpinning(false);
+ currMonitorInfo.setTraceCount(-1);
+ }
+ if (currMonitorInfo.getCpuUtilization() > _busyThreshold)
+ {
+ // Thread is busy
+ StackTraceElement[] lastStackTrace = currMonitorInfo.getStackTrace();
+
+ if (lastStackTrace != null
+ && matchStackTraces(lastStackTrace, entry.getValue()))
+ {
+ // Thread is spinning
+ currMonitorInfo.setSpinning(true);
+ if (count < 0)
+ {
+ // Enable logging of spin status and stack traces
+ // only if the incoming trace count is negative
+ // that indicates a new scan for this thread
+ currMonitorInfo.setTraceCount(0);
+ repeat = (_trailLength > 0);
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LOG.debug(ex);
+ }
+ return repeat;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void logThreadInfo(boolean logAll)
+ {
+ if (_monitorInfo.size() > 0)
+ {
+ // Select thread objects for all live threads
+ long[] running = getAllThreadIds();
+ List all = new ArrayList();
+ for (int idx=0; idx()
+ {
+ /* ------------------------------------------------------------ */
+ public int compare(ThreadMonitorInfo info1, ThreadMonitorInfo info2)
+ {
+ return (int)Math.signum(info2.getCpuUtilization()-info1.getCpuUtilization());
+ }
+ });
+
+ String format = "Thread '%2$s'[%3$s,id:%1$d,cpu:%4$.2f%%]%5$s";
+
+ // Log thread information for threads that exceed logging threshold
+ // or log spinning threads if their trace count is zero
+ boolean spinning=false;
+ for (ThreadMonitorInfo info : all)
+ {
+ if (logAll && info.getCpuUtilization() > _logThreshold
+ || info.isSpinning() && info.getTraceCount() == 0)
+ {
+ String message = String.format(format,
+ info.getThreadId(), info.getThreadName(),
+ info.getThreadState(), info.getCpuUtilization(),
+ info.isSpinning() ? " SPINNING" : "");
+ _logger.info(message);
+ spinning=true;
+ }
+ }
+
+ // Dump info
+ if (spinning && _dumpable!=null)
+ {
+ System.err.println(_dumpable.dump());
+ }
+
+ // Log stack traces for spinning threads with positive trace count
+ for (ThreadMonitorInfo info : all)
+ {
+ if (info.isSpinning() && info.getTraceCount() >= 0)
+ {
+ String message = String.format(format,
+ info.getThreadId(), info.getThreadName(),
+ info.getThreadState(), info.getCpuUtilization(),
+ " STACK TRACE");
+ _logger.warn(new ThreadMonitorException(message, info.getStackTrace()));
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match stack traces.
+ *
+ * @param lastStackTrace last stack trace
+ * @param stackTrace current stack trace
+ * @return true, if successful
+ */
+ private boolean matchStackTraces(StackTraceElement[] lastStackTrace, StackTraceElement[] stackTrace)
+ {
+ boolean match = true;
+ int count = Math.min(_stackDepth, Math.min(lastStackTrace.length, stackTrace.length));
+
+ for (int idx=0; idx < count; idx++)
+ {
+ if (!stackTrace[idx].equals(lastStackTrace[idx]))
+ {
+ match = false;
+ break;
+ }
+ }
+ return match;
+ }
+}
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java
new file mode 100644
index 00000000000..99bbeab5830
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java
@@ -0,0 +1,419 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.monitor.integration;
+
+import static java.lang.Integer.parseInt;
+import static java.lang.System.getProperty;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.monitor.JMXMonitor;
+import org.eclipse.jetty.monitor.jmx.EventNotifier;
+import org.eclipse.jetty.monitor.jmx.EventState;
+import org.eclipse.jetty.monitor.jmx.EventState.TriggerState;
+import org.eclipse.jetty.monitor.jmx.EventTrigger;
+import org.eclipse.jetty.monitor.jmx.MonitorAction;
+import org.eclipse.jetty.monitor.triggers.AggregateEventTrigger;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class JavaMonitorAction extends MonitorAction
+{
+ private static final Logger LOG = Log.getLogger(JavaMonitorAction.class);
+
+ private final HttpClient _client;
+
+ private final String _url;
+ private final String _uuid;
+ private final String _appid;
+
+ private String _srvip;
+ private String _session;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param notifier
+ * @param pollInterval
+ * @throws Exception
+ * @throws MalformedObjectNameException
+ */
+ public JavaMonitorAction(EventNotifier notifier, String url, String uuid, String appid, long pollInterval)
+ throws Exception
+ {
+ super(new AggregateEventTrigger(),notifier,pollInterval);
+
+ _url = url;
+ _uuid = uuid;
+ _appid = appid;
+
+ QueuedThreadPool executor = new QueuedThreadPool();
+ executor.setName(executor.getName() + "-monitor");
+ _client = new HttpClient();
+ _client.setExecutor(executor);
+
+ try
+ {
+ _client.start();
+ _srvip = getServerIP();
+ }
+ catch (Exception ex)
+ {
+ LOG.debug(ex);
+ }
+
+ sendData(new Properties());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.monitor.jmx.MonitorAction#execute(org.eclipse.jetty.monitor.jmx.EventTrigger, org.eclipse.jetty.monitor.jmx.EventState, long)
+ */
+ @Override
+ public void execute(EventTrigger trigger, EventState> state, long timestamp)
+ {
+ exec(trigger, state, timestamp);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trigger
+ * @param state
+ * @param timestamp
+ */
+ private void exec(EventTrigger trigger, EventState state, long timestamp)
+ {
+ Collection> trs = state.values();
+
+ Properties data = new Properties();
+ for (TriggerState ts : trs)
+ {
+ Object value = ts.getValue();
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(value == null ? "" : value.toString());
+ buffer.append("|");
+ buffer.append(getClassID(value));
+ buffer.append("||");
+ buffer.append(ts.getDescription());
+
+ data.setProperty(ts.getID(), buffer.toString());
+
+ try
+ {
+ sendData(data);
+ }
+ catch (Exception ex)
+ {
+ LOG.debug(ex);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param data
+ * @throws Exception
+ */
+ private void sendData(Properties data)
+ throws Exception
+ {
+ data.put("account", _uuid);
+ data.put("appserver", "Jetty");
+ data.put("localIp", _srvip);
+ if (_appid == null)
+ data.put("lowestPort", getHttpPort());
+ else
+ data.put("lowestPort", _appid);
+ if (_session != null)
+ data.put("session", _session);
+
+ Properties response = sendRequest(data);
+
+ parseResponse(response);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param request
+ * @return
+ * @throws Exception
+ */
+ private Properties sendRequest(Properties request)
+ throws Exception
+ {
+ ByteArrayOutputStream reqStream = null;
+ ByteArrayInputStream resStream = null;
+ Properties response = null;
+
+ try {
+ reqStream = new ByteArrayOutputStream();
+ request.storeToXML(reqStream,null);
+
+ ContentResponse r3sponse = _client.POST(_url)
+ .header("Connection","close")
+ .content(new BytesContentProvider(reqStream.toByteArray()))
+ .send();
+
+
+ if (r3sponse.getStatus() == HttpStatus.OK_200)
+ {
+ response = new Properties();
+ resStream = new ByteArrayInputStream(r3sponse.getContent());
+ response.loadFromXML(resStream);
+ }
+ }
+ finally
+ {
+ try
+ {
+ if (reqStream != null)
+ reqStream.close();
+ }
+ catch (IOException ex)
+ {
+ LOG.ignore(ex);
+ }
+
+ try
+ {
+ if (resStream != null)
+ resStream.close();
+ }
+ catch (IOException ex)
+ {
+ LOG.ignore(ex);
+ }
+ }
+
+ return response;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void parseResponse(Properties response)
+ {
+ if (response.get("onhold") != null)
+ throw new Error("Suspended");
+
+
+ if (response.get("session") != null)
+ {
+ _session = (String) response.remove("session");
+
+ AggregateEventTrigger trigger = (AggregateEventTrigger)getTrigger();
+
+ String queryString;
+ ObjectName[] queryResults;
+ for (Map.Entry
* @param
*/
-public class ArrayTrie implements Trie
+public class ArrayTrie extends AbstractTrie
{
/**
* The Size of a Trie row is how many characters can be looked
@@ -99,6 +101,7 @@ public class ArrayTrie implements Trie
public ArrayTrie(int capacityInNodes)
{
+ super(true);
_value=new Object[capacityInNodes];
_rowIndex=new char[capacityInNodes*32];
_key=new String[capacityInNodes];
@@ -154,32 +157,14 @@ public class ArrayTrie implements Trie
return true;
}
-
/* ------------------------------------------------------------ */
@Override
- public boolean put(V v)
- {
- return put(v.toString(),v);
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public V remove(String s)
- {
- V o=get(s);
- put(s,null);
- return o;
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public V get(String s)
+ public V get(String s, int offset, int len)
{
int t = 0;
- int len = s.length();
for(int i=0; i < len; i++)
{
- char c=s.charAt(i);
+ char c=s.charAt(offset+i);
int index=__lookup[c&0x7f];
if (index>=0)
{
@@ -245,7 +230,53 @@ public class ArrayTrie implements Trie
return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
return getBest(0,b,offset,len);
}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(String s, int offset, int len)
+ {
+ return getBest(0,s,offset,len);
+ }
+ /* ------------------------------------------------------------ */
+ private V getBest(int t, String s, int offset, int len)
+ {
+ int pos=offset;
+ for(int i=0; i < len; i++)
+ {
+ char c=s.charAt(pos++);
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ t=_rowIndex[idx];
+ if (t==0)
+ return null;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ t=big[c];
+ if (t==0)
+ return null;
+ }
+
+ // Is the next Trie is a match
+ if (_key[t]!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=getBest(t,s,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ return (V)_value[t];
+ }
+ }
+ return (V)_value[t];
+ }
+
+ /* ------------------------------------------------------------ */
private V getBest(int t,byte[] b,int offset,int len)
{
for(int i=0; i < len; i++)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
index 36b7e46d136..7d34cfc5e01 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
@@ -53,12 +53,12 @@ public class BlockingArrayQueue extends AbstractList implements BlockingQu
* by 15 slots to avoid false sharing with the array length
* (stored before the first element of the array itself).
*/
- private static final int HEAD_OFFSET = 15;
+ private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
/**
* The tail offset in the {@link #_indexes} array, displaced
* by 16 slots from the head to avoid false sharing with it.
*/
- private static final int TAIL_OFFSET = 31;
+ private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
/**
* Default initial capacity, 128.
*/
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
new file mode 100644
index 00000000000..5020795ad1f
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
@@ -0,0 +1,105 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * ClassLoadingObjectInputStream
+ *
+ * For re-inflating serialized objects, this class uses the thread context classloader
+ * rather than the jvm's default classloader selection.
+ *
+ */
+public class ClassLoadingObjectInputStream extends ObjectInputStream
+{
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+ {
+ super(in);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream () throws IOException
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Class> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+ {
+ try
+ {
+ return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+ }
+ catch (ClassNotFoundException e)
+ {
+ return super.resolveClass(cl);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected Class> resolveProxyClass(String[] interfaces)
+ throws IOException, ClassNotFoundException
+ {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+ ClassLoader nonPublicLoader = null;
+ boolean hasNonPublicInterface = false;
+
+ // define proxy in class loader of non-public interface(s), if any
+ Class[] classObjs = new Class[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++)
+ {
+ Class cl = Class.forName(interfaces[i], false, loader);
+ if ((cl.getModifiers() & Modifier.PUBLIC) == 0)
+ {
+ if (hasNonPublicInterface)
+ {
+ if (nonPublicLoader != cl.getClassLoader())
+ {
+ throw new IllegalAccessError(
+ "conflicting non-public interface class loaders");
+ }
+ }
+ else
+ {
+ nonPublicLoader = cl.getClassLoader();
+ hasNonPublicInterface = true;
+ }
+ }
+ classObjs[i] = cl;
+ }
+ try
+ {
+ return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new ClassNotFoundException(null, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java
new file mode 100644
index 00000000000..ca9a109eab1
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java
@@ -0,0 +1,418 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Common functionality for a blocking version of {@link ConcurrentArrayQueue}.
+ *
+ * @see Unbounded
+ * @see Bounded
+ * @param
+ */
+public abstract class ConcurrentArrayBlockingQueue extends ConcurrentArrayQueue implements BlockingQueue
+{
+ private final Lock _lock = new ReentrantLock();
+ private final Condition _consumer = _lock.newCondition();
+
+ public ConcurrentArrayBlockingQueue(int blockSize)
+ {
+ super(blockSize);
+ }
+
+ @Override
+ public E poll()
+ {
+ E result = super.poll();
+ if (result != null && decrementAndGetSize() > 0)
+ signalConsumer();
+ return result;
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ boolean result = super.remove(o);
+ if (result && decrementAndGetSize() > 0)
+ signalConsumer();
+ return result;
+ }
+
+ protected abstract int decrementAndGetSize();
+
+ protected void signalConsumer()
+ {
+ final Lock lock = _lock;
+ lock.lock();
+ try
+ {
+ _consumer.signal();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public E take() throws InterruptedException
+ {
+ while (true)
+ {
+ E result = poll();
+ if (result != null)
+ return result;
+
+ final Lock lock = _lock;
+ lock.lockInterruptibly();
+ try
+ {
+ if (size() == 0)
+ {
+ _consumer.await();
+ }
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public E poll(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ long nanos = unit.toNanos(timeout);
+
+ while (true)
+ {
+ // TODO should reduce nanos if we spin here
+
+ E result = poll();
+ if (result != null)
+ return result;
+
+ final Lock lock = _lock;
+ lock.lockInterruptibly();
+ try
+ {
+ if (size() == 0)
+ {
+ if (nanos <= 0)
+ return null;
+ nanos = _consumer.awaitNanos(nanos);
+ }
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public int drainTo(Collection super E> c)
+ {
+ return drainTo(c, Integer.MAX_VALUE);
+ }
+
+ @Override
+ public int drainTo(Collection super E> c, int maxElements)
+ {
+ if (c == this)
+ throw new IllegalArgumentException();
+
+ int added = 0;
+ while (added < maxElements)
+ {
+ E element = poll();
+ if (element == null)
+ break;
+ c.add(element);
+ ++added;
+ }
+ return added;
+ }
+
+ /**
+ * An unbounded, blocking version of {@link ConcurrentArrayQueue}.
+ *
+ * @param
+ */
+ public static class Unbounded extends ConcurrentArrayBlockingQueue
+ {
+ private static final int SIZE_LEFT_OFFSET = MemoryUtils.getLongsPerCacheLine() - 1;
+ private static final int SIZE_RIGHT_OFFSET = SIZE_LEFT_OFFSET + MemoryUtils.getLongsPerCacheLine();
+
+ private final AtomicLongArray _sizes = new AtomicLongArray(SIZE_RIGHT_OFFSET+1);
+
+ public Unbounded()
+ {
+ this(DEFAULT_BLOCK_SIZE);
+ }
+
+ public Unbounded(int blockSize)
+ {
+ super(blockSize);
+ }
+
+ @Override
+ public boolean offer(E item)
+ {
+ boolean result = super.offer(item);
+ if (result && getAndIncrementSize() == 0)
+ signalConsumer();
+ return result;
+ }
+
+ private int getAndIncrementSize()
+ {
+ long sizeRight = _sizes.getAndIncrement(SIZE_RIGHT_OFFSET);
+ long sizeLeft = _sizes.get(SIZE_LEFT_OFFSET);
+ return (int)(sizeRight - sizeLeft);
+ }
+
+ @Override
+ protected int decrementAndGetSize()
+ {
+ long sizeLeft = _sizes.incrementAndGet(SIZE_LEFT_OFFSET);
+ long sizeRight = _sizes.get(SIZE_RIGHT_OFFSET);
+ return (int)(sizeRight - sizeLeft);
+ }
+
+ @Override
+ public int size()
+ {
+ long sizeLeft = _sizes.get(SIZE_LEFT_OFFSET);
+ long sizeRight = _sizes.get(SIZE_RIGHT_OFFSET);
+ return (int)(sizeRight - sizeLeft);
+ }
+
+ @Override
+ public int remainingCapacity()
+ {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public void put(E element) throws InterruptedException
+ {
+ offer(element);
+ }
+
+ @Override
+ public boolean offer(E element, long timeout, TimeUnit unit) throws InterruptedException
+ {
+ return offer(element);
+ }
+ }
+
+ /**
+ * A bounded, blocking version of {@link ConcurrentArrayQueue}.
+ *
+ * @param
+ */
+ public static class Bounded extends ConcurrentArrayBlockingQueue
+ {
+ private final AtomicInteger _size = new AtomicInteger();
+ private final Lock _lock = new ReentrantLock();
+ private final Condition _producer = _lock.newCondition();
+ private final int _capacity;
+
+ public Bounded(int capacity)
+ {
+ this(DEFAULT_BLOCK_SIZE, capacity);
+ }
+
+ public Bounded(int blockSize, int capacity)
+ {
+ super(blockSize);
+ this._capacity = capacity;
+ }
+
+ @Override
+ public boolean offer(E item)
+ {
+ while (true)
+ {
+ int size = size();
+ int nextSize = size + 1;
+
+ if (nextSize > _capacity)
+ return false;
+
+ if (_size.compareAndSet(size, nextSize))
+ {
+ if (super.offer(item))
+ {
+ if (size == 0)
+ signalConsumer();
+ return true;
+ }
+ else
+ {
+ decrementAndGetSize();
+ }
+ }
+ }
+ }
+
+ @Override
+ public E poll()
+ {
+ E result = super.poll();
+ if (result != null)
+ signalProducer();
+ return result;
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ boolean result = super.remove(o);
+ if (result)
+ signalProducer();
+ return result;
+ }
+
+ @Override
+ protected int decrementAndGetSize()
+ {
+ return _size.decrementAndGet();
+ }
+
+ @Override
+ public int size()
+ {
+ return _size.get();
+ }
+
+ @Override
+ public int remainingCapacity()
+ {
+ return _capacity - size();
+ }
+
+ @Override
+ public void put(E item) throws InterruptedException
+ {
+ item = Objects.requireNonNull(item);
+
+ while (true)
+ {
+ final Lock lock = _lock;
+ lock.lockInterruptibly();
+ try
+ {
+ if (size() == _capacity)
+ _producer.await();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ if (offer(item))
+ break;
+ }
+ }
+
+ @Override
+ public boolean offer(E item, long timeout, TimeUnit unit) throws InterruptedException
+ {
+ item = Objects.requireNonNull(item);
+
+ long nanos = unit.toNanos(timeout);
+ while (true)
+ {
+ final Lock lock = _lock;
+ lock.lockInterruptibly();
+ try
+ {
+ if (size() == _capacity)
+ {
+ if (nanos <= 0)
+ return false;
+ nanos = _producer.awaitNanos(nanos);
+ }
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ if (offer(item))
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int drainTo(Collection super E> c, int maxElements)
+ {
+ int result = super.drainTo(c, maxElements);
+ if (result > 0)
+ signalProducers();
+ return result;
+ }
+
+ @Override
+ public void clear()
+ {
+ super.clear();
+ signalProducers();
+ }
+
+ private void signalProducer()
+ {
+ final Lock lock = _lock;
+ lock.lock();
+ try
+ {
+ _producer.signal();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+
+ private void signalProducers()
+ {
+ final Lock lock = _lock;
+ lock.lock();
+ try
+ {
+ _producer.signalAll();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+ }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java
new file mode 100644
index 00000000000..b477a31e336
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java
@@ -0,0 +1,570 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A concurrent, unbounded implementation of {@link Queue} that uses singly-linked array blocks
+ * to store elements.
+ *
+ * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with similar performance
+ * but producing less garbage because arrays are used to store elements rather than nodes.
+ *
+ * The algorithm used is a variation of the algorithm from Gidenstam, Sundell and Tsigas
+ * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
+ *
+ * @param
+ */
+public class ConcurrentArrayQueue extends AbstractQueue
+{
+ public static final int DEFAULT_BLOCK_SIZE = 512;
+ public static final Object REMOVED_ELEMENT = new Object()
+ {
+ @Override
+ public String toString()
+ {
+ return "X";
+ }
+ };
+
+ private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+ private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine()*2 -1;
+
+ private final AtomicReferenceArray> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
+ private final int _blockSize;
+
+ public ConcurrentArrayQueue()
+ {
+ this(DEFAULT_BLOCK_SIZE);
+ }
+
+ public ConcurrentArrayQueue(int blockSize)
+ {
+ _blockSize = blockSize;
+ Block block = newBlock();
+ _blocks.set(HEAD_OFFSET,block);
+ _blocks.set(TAIL_OFFSET,block);
+ }
+
+ public int getBlockSize()
+ {
+ return _blockSize;
+ }
+
+ protected Block getHeadBlock()
+ {
+ return _blocks.get(HEAD_OFFSET);
+ }
+
+ protected Block getTailBlock()
+ {
+ return _blocks.get(TAIL_OFFSET);
+ }
+
+ @Override
+ public boolean offer(T item)
+ {
+ item = Objects.requireNonNull(item);
+
+ final Block initialTailBlock = getTailBlock();
+ Block currentTailBlock = initialTailBlock;
+ int tail = currentTailBlock.tail();
+ while (true)
+ {
+ if (tail == getBlockSize())
+ {
+ Block nextTailBlock = currentTailBlock.next();
+ if (nextTailBlock == null)
+ {
+ nextTailBlock = newBlock();
+ if (currentTailBlock.link(nextTailBlock))
+ {
+ // Linking succeeded, loop
+ currentTailBlock = nextTailBlock;
+ }
+ else
+ {
+ // Concurrent linking, use other block and loop
+ currentTailBlock = currentTailBlock.next();
+ }
+ }
+ else
+ {
+ // Not at last block, loop
+ currentTailBlock = nextTailBlock;
+ }
+ tail = currentTailBlock.tail();
+ }
+ else
+ {
+ if (currentTailBlock.peek(tail) == null)
+ {
+ if (currentTailBlock.store(tail, item))
+ {
+ // Item stored
+ break;
+ }
+ else
+ {
+ // Concurrent store, try next index
+ ++tail;
+ }
+ }
+ else
+ {
+ // Not free, try next index
+ ++tail;
+ }
+ }
+ }
+
+ updateTailBlock(initialTailBlock, currentTailBlock);
+
+ return true;
+ }
+
+ private void updateTailBlock(Block oldTailBlock, Block newTailBlock)
+ {
+ // Update the tail block pointer if needs to
+ if (oldTailBlock != newTailBlock)
+ {
+ // The tail block pointer is allowed to lag behind.
+ // If this update fails, it means that other threads
+ // have filled this block and installed a new one.
+ casTailBlock(oldTailBlock, newTailBlock);
+ }
+ }
+
+ protected boolean casTailBlock(Block current, Block update)
+ {
+ return _blocks.compareAndSet(TAIL_OFFSET,current,update);
+ }
+
+ @Override
+ public T poll()
+ {
+ final Block initialHeadBlock = getHeadBlock();
+ Block currentHeadBlock = initialHeadBlock;
+ int head = currentHeadBlock.head();
+ T result = null;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // We could have read that the next head block was null
+ // but another thread allocated a new block and stored a
+ // new item. This thread could not detect this, but that
+ // is ok, otherwise we would not be able to exit this loop.
+
+ // Queue is empty
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else
+ {
+ result = (T)element;
+ if (result != null)
+ {
+ if (currentHeadBlock.remove(head, result, true))
+ {
+ // Item removed
+ break;
+ }
+ else
+ {
+ // Concurrent remove, try next index
+ ++head;
+ }
+ }
+ else
+ {
+ // Queue is empty
+ break;
+ }
+ }
+ }
+ }
+
+ updateHeadBlock(initialHeadBlock, currentHeadBlock);
+
+ return result;
+ }
+
+ private void updateHeadBlock(Block oldHeadBlock, Block newHeadBlock)
+ {
+ // Update the head block pointer if needs to
+ if (oldHeadBlock != newHeadBlock)
+ {
+ // The head block pointer lagged behind.
+ // If this update fails, it means that other threads
+ // have emptied this block and pointed to a new one.
+ casHeadBlock(oldHeadBlock, newHeadBlock);
+ }
+ }
+
+ protected boolean casHeadBlock(Block current, Block update)
+ {
+ return _blocks.compareAndSet(HEAD_OFFSET,current,update);
+ }
+
+ @Override
+ public T peek()
+ {
+ Block currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // Queue is empty
+ return null;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else
+ {
+ return (T)element;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ Block currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ boolean result = false;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // Not found
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Removed, try next index
+ ++head;
+ }
+ else
+ {
+ if (element == null)
+ {
+ // Not found
+ break;
+ }
+ else
+ {
+ if (element.equals(o))
+ {
+ // Found
+ if (currentHeadBlock.remove(head, o, false))
+ {
+ result = true;
+ break;
+ }
+ else
+ {
+ ++head;
+ }
+ }
+ else
+ {
+ // Not the one we're looking for
+ ++head;
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean removeAll(Collection> c)
+ {
+ // TODO: super invocations are based on iterator.remove(), which throws
+ return super.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection> c)
+ {
+ // TODO: super invocations are based on iterator.remove(), which throws
+ return super.retainAll(c);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ final List blocks = new ArrayList<>();
+ Block currentHeadBlock = getHeadBlock();
+ while (currentHeadBlock != null)
+ {
+ Object[] elements = currentHeadBlock.arrayCopy();
+ blocks.add(elements);
+ currentHeadBlock = currentHeadBlock.next();
+ }
+ return new Iterator()
+ {
+ private int blockIndex;
+ private int index;
+
+ @Override
+ public boolean hasNext()
+ {
+ while (true)
+ {
+ if (blockIndex == blocks.size())
+ return false;
+
+ Object element = blocks.get(blockIndex)[index];
+
+ if (element == null)
+ return false;
+
+ if (element != REMOVED_ELEMENT)
+ return true;
+
+ advance();
+ }
+ }
+
+ @Override
+ public T next()
+ {
+ while (true)
+ {
+ if (blockIndex == blocks.size())
+ throw new NoSuchElementException();
+
+ Object element = blocks.get(blockIndex)[index];
+
+ if (element == null)
+ throw new NoSuchElementException();
+
+ advance();
+
+ if (element != REMOVED_ELEMENT)
+ return (T)element;
+ }
+ }
+
+ private void advance()
+ {
+ if (++index == getBlockSize())
+ {
+ index = 0;
+ ++blockIndex;
+ }
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public int size()
+ {
+ Block currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ int size = 0;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else if (element != null)
+ {
+ ++size;
+ ++head;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ return size;
+ }
+
+ protected Block newBlock()
+ {
+ return new Block<>(getBlockSize());
+ }
+
+ protected int getBlockCount()
+ {
+ int result = 0;
+ Block headBlock = getHeadBlock();
+ while (headBlock != null)
+ {
+ ++result;
+ headBlock = headBlock.next();
+ }
+ return result;
+ }
+
+ protected static final class Block
+ {
+ private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1;
+ private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1;
+
+ private final AtomicReferenceArray elements;
+ private final AtomicReference> next = new AtomicReference<>();
+ private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET+1);
+
+ protected Block(int blockSize)
+ {
+ elements = new AtomicReferenceArray<>(blockSize);
+ }
+
+ public Object peek(int index)
+ {
+ return elements.get(index);
+ }
+
+ public boolean store(int index, E item)
+ {
+ boolean result = elements.compareAndSet(index, null, item);
+ if (result)
+ indexes.incrementAndGet(tailOffset);
+ return result;
+ }
+
+ public boolean remove(int index, Object item, boolean updateHead)
+ {
+ boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT);
+ if (result && updateHead)
+ indexes.incrementAndGet(headOffset);
+ return result;
+ }
+
+ public Block next()
+ {
+ return next.get();
+ }
+
+ public boolean link(Block nextBlock)
+ {
+ return next.compareAndSet(null, nextBlock);
+ }
+
+ public int head()
+ {
+ return indexes.get(headOffset);
+ }
+
+ public int tail()
+ {
+ return indexes.get(tailOffset);
+ }
+
+ public Object[] arrayCopy()
+ {
+ Object[] result = new Object[elements.length()];
+ for (int i = 0; i < result.length; ++i)
+ result[i] = elements.get(i);
+ return result;
+ }
+ }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
new file mode 100644
index 00000000000..62d7039f73d
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * {@link MemoryUtils} provides an abstraction over memory properties and operations.
+ *
+ */
+public class MemoryUtils
+{
+ private static final int cacheLineBytes;
+ static
+ {
+ final int defaultValue = 64;
+ int value = defaultValue;
+ try
+ {
+ value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction()
+ {
+ @Override
+ public String run()
+ {
+ return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
+ }
+ }));
+ }
+ catch (Exception ignored)
+ {
+ }
+ cacheLineBytes = value;
+ }
+
+ private MemoryUtils()
+ {
+ }
+
+ public static int getCacheLineBytes()
+ {
+ return cacheLineBytes;
+ }
+
+ public static int getIntegersPerCacheLine()
+ {
+ return getCacheLineBytes() >> 2;
+ }
+
+ public static int getLongsPerCacheLine()
+ {
+ return getCacheLineBytes() >> 3;
+ }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
index 725d5ea3779..be25e7bf76f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
@@ -569,8 +569,13 @@ public class Scanner extends AbstractLifeCycle
if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
{
File[] files = f.listFiles();
- for (int i=0;i extends AbstractMap
{
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 59426d48ea3..29db2113ad1 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
@@ -39,7 +39,7 @@ public class StringUtil
private static final Logger LOG = Log.getLogger(StringUtil.class);
- private final static StringMap CHARSETS= new StringMap(true);
+ private final static Trie CHARSETS= new ArrayTrie<>(256);
public static final String ALL_INTERFACES="0.0.0.0";
public static final String CRLF="\015\012";
@@ -87,28 +87,9 @@ public class StringUtil
String n=CHARSETS.get(s,offset,length);
return (n==null)?s.substring(offset,offset+length):n;
}
+
/* ------------------------------------------------------------ */
- /** Convert alternate charset names (eg utf8) to normalized
- * name (eg UTF-8).
- */
- public static String normalizeCharset(ByteBuffer b,int position,int length)
- {
- ByteBuffer ro=b.asReadOnlyBuffer();
- ro.limit(ro.capacity());
- ro.position(position);
- ro.limit(position+length);
- String n=CHARSETS.get(ro);
- if (n!=null)
- return n;
- ByteBuffer slice = b.slice();
- slice.position(position);
- slice.limit(position+length);
- return BufferUtil.toString(slice,__UTF8_CHARSET);
- }
-
-
-
public static char[] lowercases = {
'\000','\001','\002','\003','\004','\005','\006','\007',
'\010','\011','\012','\013','\014','\015','\016','\017',
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
index 417bbce30b2..484a642040f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
@@ -25,7 +25,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-public class TreeTrie implements Trie
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a tree
+ * This implementation is always case insensitive and is optimal for
+ * a variable number of fixed strings with few special characters.
+ *
+ * @param
+ */
+public class TreeTrie extends AbstractTrie
{
private static final int[] __lookup =
{ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
@@ -47,22 +54,18 @@ public class TreeTrie implements Trie
public TreeTrie()
{
+ super(true);
_nextIndex = new TreeTrie[INDEX];
_c=0;
}
private TreeTrie(char c)
{
+ super(true);
_nextIndex = new TreeTrie[INDEX];
this._c=c;
}
- @Override
- public boolean put(V v)
- {
- return put(v.toString(),v);
- }
-
@Override
public boolean put(String s, V v)
{
@@ -105,21 +108,12 @@ public class TreeTrie implements Trie
}
@Override
- public V remove(String s)
- {
- V o=get(s);
- put(s,null);
- return o;
- }
-
- @Override
- public V get(String s)
+ public V get(String s,int offset, int len)
{
TreeTrie t = this;
- int len = s.length();
for(int i=0; i < len; i++)
{
- char c=s.charAt(i);
+ char c=s.charAt(offset+i);
int index=c>=0&&c<0x7f?__lookup[c]:-1;
if (index>=0)
{
@@ -219,6 +213,14 @@ public class TreeTrie implements Trie
return t._value;
}
+ @Override
+ public V getBest(String s, int offset, int len)
+ {
+ // TODO inefficient
+ byte[] b=s.substring(offset,offset+len).getBytes(StringUtil.__ISO_8859_1_CHARSET);
+ return getBest(b,0,b.length);
+ }
+
@Override
public V getBest(ByteBuffer b,int offset,int len)
{
@@ -343,5 +345,6 @@ public class TreeTrie implements Trie
{
return false;
}
-
+
+
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
index 3bf17ac20e9..1b095215307 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
@@ -52,7 +52,23 @@ public interface Trie
* @return
*/
public V get(String s);
-
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a String key
+ * @param s The key
+ * @param offset The offset within the string of the key
+ * @param len the length of the key
+ * @return
+ */
+ public V get(String s,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a segment of a ByteBuufer as key
+ * @param b The buffer
+ * @return The value or null if not found
+ */
+ public V get(ByteBuffer b);
+
/* ------------------------------------------------------------ */
/** Get and exact match from a segment of a ByteBuufer as key
* @param b The buffer
@@ -61,6 +77,22 @@ public interface Trie
* @return The value or null if not found
*/
public V get(ByteBuffer b,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a String.
+ * @param s The string
+ * @return The value or null if not found
+ */
+ public V getBest(String s);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a String.
+ * @param s The string
+ * @param offset The offset within the string of the key
+ * @param len the length of the key
+ * @return The value or null if not found
+ */
+ public V getBest(String s,int offset,int len);
/* ------------------------------------------------------------ */
/** Get the best match from key in a byte array.
@@ -81,10 +113,14 @@ public interface Trie
* @return The value or null if not found
*/
public V getBest(ByteBuffer b,int offset,int len);
-
+
/* ------------------------------------------------------------ */
public Set keySet();
-
+
+ /* ------------------------------------------------------------ */
public boolean isFull();
-
+
+ /* ------------------------------------------------------------ */
+ public boolean isCaseInsensitive();
+
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
index 8d3f7cf1d16..b514a5855dd 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
@@ -53,31 +53,26 @@ public class FileNoticeLifeCycleListener implements LifeCycle.Listener
}
}
- @Override
public void lifeCycleStarting(LifeCycle event)
{
writeState("STARTING",event);
}
- @Override
public void lifeCycleStarted(LifeCycle event)
{
writeState("STARTED",event);
}
- @Override
public void lifeCycleFailure(LifeCycle event, Throwable cause)
{
writeState("FAILED",event);
}
- @Override
public void lifeCycleStopping(LifeCycle event)
{
writeState("STOPPING",event);
}
- @Override
public void lifeCycleStopped(LifeCycle event)
{
writeState("STOPPED",event);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index 1c6b85b0581..dcfa977da5d 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -54,7 +54,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
private final AtomicLong _lastShrink = new AtomicLong();
private final ConcurrentLinkedQueue _threads = new ConcurrentLinkedQueue<>();
private final Object _joinLock = new Object();
- private BlockingQueue _jobs;
+ private final BlockingQueue _jobs;
private String _name = "qtp" + hashCode();
private int _idleTimeout;
private int _maxThreads;
@@ -68,22 +68,32 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
this(200);
}
- public QueuedThreadPool(int maxThreads)
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads)
{
this(maxThreads, 8);
}
- public QueuedThreadPool(int maxThreads, int minThreads)
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads)
{
this(maxThreads, minThreads, 60000);
}
- public QueuedThreadPool(int maxThreads, int minThreads, int idleTimeout)
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
+ {
+ this(maxThreads, minThreads, 60000,null);
+ }
+
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue queue)
{
setMinThreads(minThreads);
setMaxThreads(maxThreads);
setIdleTimeout(idleTimeout);
setStopTimeout(5000);
+
+ if (queue==null)
+ queue=new BlockingArrayQueue(_minThreads, _minThreads);// TODO ConcurrentArrayBlockingQueue.Unbounded();
+ _jobs=queue;
+
}
@Override
@@ -92,9 +102,6 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
super.doStart();
_threadsStarted.set(0);
- if (_jobs == null)
- setQueue(new BlockingArrayQueue(_minThreads, _minThreads));
-
startThreads(_minThreads);
}
@@ -602,7 +609,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
*/
public void setQueue(BlockingQueue queue)
{
- _jobs = queue;
+ throw new UnsupportedOperationException("Use constructor injection");
}
/**
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java
new file mode 100644
index 00000000000..8545f91f057
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java
@@ -0,0 +1,166 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ConcurrentArrayBlockingQueueUnboundedTest extends ConcurrentArrayQueueTest
+{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+
+ @Override
+ protected ConcurrentArrayBlockingQueue newConcurrentArrayQueue(int blockSize)
+ {
+ return new ConcurrentArrayBlockingQueue.Unbounded<>(blockSize);
+ }
+
+ @Test
+ public void testOfferTake() throws Exception
+ {
+ ConcurrentArrayBlockingQueue queue = newConcurrentArrayQueue(32);
+ Integer item = 1;
+ Assert.assertTrue(queue.offer(item));
+ Integer result = queue.take();
+ Assert.assertSame(item, result);
+ }
+
+ @Test
+ public void testTimedPollOffer() throws Exception
+ {
+ final ConcurrentArrayBlockingQueue queue = newConcurrentArrayQueue(32);
+
+ final long timeout = 1000;
+ final Integer item = 1;
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ TimeUnit.MILLISECONDS.sleep(timeout);
+ queue.offer(item);
+ }
+ catch (InterruptedException x)
+ {
+ x.printStackTrace();
+ }
+ }
+ }.start();
+
+ Integer result = queue.poll(2 * timeout, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(result);
+ }
+
+ @Test
+ public void testConcurrentOfferTake() throws Exception
+ {
+ final ConcurrentArrayBlockingQueue queue = newConcurrentArrayQueue(512);
+ int readerCount = 16;
+ final int factor = 2;
+ int writerCount = readerCount * factor;
+ final int iterations = 4096;
+ for (int runs = 0; runs < 16; ++runs)
+ {
+ ExecutorService executor = Executors.newFixedThreadPool(readerCount + writerCount);
+ List> readers = new ArrayList<>();
+ for (int i = 0; i < readerCount / 2; ++i)
+ {
+ final int reader = i;
+ readers.add(executor.submit(new Callable()
+ {
+ @Override
+ public Integer call() throws Exception
+ {
+ int sum = 0;
+ for (int j = 0; j < iterations * factor; ++j)
+ sum += queue.take();
+ //System.err.println("Taking reader " + reader + " completed: " + sum);
+ return sum;
+ }
+ }));
+ readers.add(executor.submit(new Callable()
+ {
+ @Override
+ public Integer call() throws Exception
+ {
+ int sum = 0;
+ for (int j = 0; j < iterations * factor; ++j)
+ sum += queue.poll(5, TimeUnit.SECONDS);
+ //System.err.println("Polling Reader " + reader + " completed: " + sum);
+ return sum;
+ }
+ }));
+ }
+ for (int i = 0; i < writerCount; ++i)
+ {
+ final int writer = i;
+ executor.submit(new Callable()
+ {
+ @Override
+ public Object call() throws Exception
+ {
+ for (int j = 0; j < iterations; ++j)
+ queue.offer(1);
+ //System.err.println("Writer " + writer + " completed");
+ return null;
+ }
+ });
+ }
+
+ int sum = 0;
+ for (Future result : readers)
+ sum += result.get();
+
+ Assert.assertEquals(writerCount * iterations, sum);
+ Assert.assertTrue(queue.isEmpty());
+ }
+ }
+
+ @Test
+ public void testDrain() throws Exception
+ {
+ final ConcurrentArrayBlockingQueue queue = newConcurrentArrayQueue(512);
+ List chunk1 = Arrays.asList(1, 2);
+ List chunk2 = Arrays.asList(3, 4, 5);
+ queue.addAll(chunk1);
+ queue.addAll(chunk2);
+
+ List drainer1 = new ArrayList<>();
+ queue.drainTo(drainer1, chunk1.size());
+ List drainer2 = new ArrayList<>();
+ queue.drainTo(drainer2, chunk2.size());
+
+ Assert.assertEquals(chunk1, drainer1);
+ Assert.assertEquals(chunk2, drainer2);
+ }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java
new file mode 100644
index 00000000000..3f5e7cddd4f
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConcurrentArrayQueueTest
+{
+ protected ConcurrentArrayQueue newConcurrentArrayQueue(int blockSize)
+ {
+ return new ConcurrentArrayQueue<>(blockSize);
+ }
+
+ @Test
+ public void testOfferCreatesBlock()
+ {
+ int blockSize = 2;
+ ConcurrentArrayQueue queue = newConcurrentArrayQueue(blockSize);
+ int blocks = 3;
+ for (int i = 0; i < blocks * blockSize + 1; ++i)
+ queue.offer(i);
+ Assert.assertEquals(blocks + 1, queue.getBlockCount());
+ }
+
+ @Test
+ public void testPeekRemove() throws Exception
+ {
+ int blockSize = 2;
+ ConcurrentArrayQueue queue = newConcurrentArrayQueue(blockSize);
+
+ Assert.assertNull(queue.peek());
+
+ queue.offer(1);
+ queue.remove(1);
+ Assert.assertNull(queue.peek());
+
+ int blocks = 3;
+ int size = blocks * blockSize + 1;
+ for (int i = 0; i < size; ++i)
+ queue.offer(i);
+ for (int i = 0; i < size; ++i)
+ {
+ Assert.assertEquals(i, (int)queue.peek());
+ Assert.assertEquals(i, (int)queue.remove());
+ }
+ }
+
+ @Test
+ public void testRemoveObject() throws Exception
+ {
+ int blockSize = 2;
+ ConcurrentArrayQueue queue = newConcurrentArrayQueue(blockSize);
+ queue.add(1);
+ queue.add(2);
+ queue.add(3);
+
+ Assert.assertFalse(queue.remove(4));
+
+ int size = queue.size();
+
+ Assert.assertTrue(queue.remove(2));
+ --size;
+ Assert.assertEquals(size, queue.size());
+
+ Iterator iterator = queue.iterator();
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(1, (int)iterator.next());
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(3, (int)iterator.next());
+
+ queue.offer(4);
+ ++size;
+
+ Assert.assertTrue(queue.remove(3));
+ --size;
+ Assert.assertEquals(size, queue.size());
+
+ iterator = queue.iterator();
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(1, (int)iterator.next());
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(4, (int)iterator.next());
+
+ Assert.assertTrue(queue.remove(1));
+ --size;
+ Assert.assertTrue(queue.remove(4));
+ --size;
+
+ iterator = queue.iterator();
+ Assert.assertFalse(iterator.hasNext());
+ }
+
+ @Test
+ public void testSize() throws Exception
+ {
+ int blockSize = 2;
+ ConcurrentArrayQueue queue = newConcurrentArrayQueue(blockSize);
+ queue.offer(1);
+ Assert.assertEquals(1, queue.size());
+
+ queue = newConcurrentArrayQueue(blockSize);
+ for (int i = 0; i < 2 * blockSize; ++i)
+ queue.offer(i);
+ for (int i = 0; i < blockSize; ++i)
+ queue.poll();
+ Assert.assertEquals(blockSize, queue.size());
+ }
+
+ @Test
+ public void testIterator() throws Exception
+ {
+ int blockSize = 2;
+ ConcurrentArrayQueue queue = newConcurrentArrayQueue(blockSize);
+ queue.offer(1);
+ Iterator iterator = queue.iterator();
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(1, (int)iterator.next());
+ Assert.assertFalse(iterator.hasNext());
+
+ try
+ {
+ iterator.next();
+ Assert.fail();
+ }
+ catch (NoSuchElementException ignored)
+ {
+ }
+
+ // Test block edge
+ queue = newConcurrentArrayQueue(blockSize);
+ for (int i = 0; i < blockSize * 2; ++i)
+ queue.offer(i);
+ queue.poll();
+ iterator = queue.iterator();
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(1, (int)iterator.next());
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(2, (int)iterator.next());
+ Assert.assertTrue(iterator.hasNext());
+ Assert.assertEquals(3, (int)iterator.next());
+ Assert.assertFalse(iterator.hasNext());
+
+ try
+ {
+ iterator.next();
+ Assert.fail();
+ }
+ catch (NoSuchElementException ignored)
+ {
+ }
+ }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
index 1febc2f26d5..8fe5f6ea486 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
@@ -82,7 +82,8 @@ public class TrieTest
Assert.assertEquals(5,trie.get("wobble").intValue());
Assert.assertEquals(6,trie.get("Foo-bar").intValue());
Assert.assertEquals(7,trie.get("FOO+bar").intValue());
-
+
+ Assert.assertEquals(null,trie.get("helloworld"));
Assert.assertEquals(null,trie.get("Help"));
Assert.assertEquals(null,trie.get("Blah"));
}
@@ -105,7 +106,8 @@ public class TrieTest
Assert.assertEquals(5,trie.get(BufferUtil.toBuffer("xwobble"),1,6).intValue());
Assert.assertEquals(6,trie.get(BufferUtil.toBuffer("xFOO-barx"),1,7).intValue());
Assert.assertEquals(7,trie.get(BufferUtil.toBuffer("xFOO+barx"),1,7).intValue());
-
+
+ Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xHelloworldx"),1,10));
Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xHelpx"),1,4));
Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xBlahx"),1,4));
}
@@ -128,7 +130,8 @@ public class TrieTest
Assert.assertEquals(5,trie.get(BufferUtil.toDirectBuffer("xwobble"),1,6).intValue());
Assert.assertEquals(6,trie.get(BufferUtil.toDirectBuffer("xFOO-barx"),1,7).intValue());
Assert.assertEquals(7,trie.get(BufferUtil.toDirectBuffer("xFOO+barx"),1,7).intValue());
-
+
+ Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xHelloworldx"),1,10));
Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xHelpx"),1,4));
Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xBlahx"),1,4));
}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
index 4849840bad5..eee8191c151 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
@@ -57,7 +57,7 @@ public class MetaData
protected final Map> _webFragmentAnnotations = new HashMap>();
protected final List _webInfJars = new ArrayList();
protected final List _orderedWebInfJars = new ArrayList();
- protected final List _orderedContainerJars = new ArrayList();
+ protected final List _orderedContainerResources = new ArrayList();
protected Ordering _ordering;//can be set to RelativeOrdering by web-default.xml, web.xml, web-override.xml
protected boolean allowDuplicateFragmentNames = false;
@@ -137,7 +137,7 @@ public class MetaData
_webFragmentAnnotations.clear();
_webInfJars.clear();
_orderedWebInfJars.clear();
- _orderedContainerJars.clear();
+ _orderedContainerResources.clear();
_ordering = null;
allowDuplicateFragmentNames = false;
}
@@ -564,14 +564,14 @@ public class MetaData
return Collections.unmodifiableList(_webInfJars);
}
- public List getOrderedContainerJars()
+ public List getContainerResources()
{
- return _orderedContainerJars;
+ return _orderedContainerResources;
}
- public void addContainerJar(Resource jar)
+ public void addContainerResource(Resource jar)
{
- _orderedContainerJars.add(jar);
+ _orderedContainerResources.add(jar);
}
public boolean isAllowDuplicateFragmentNames()
{
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 37d16201107..df913b508da 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
@@ -53,7 +53,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
//Merge all container and webinf lib jars to look for META-INF resources
ArrayList jars = new ArrayList();
- jars.addAll(context.getMetaData().getOrderedContainerJars());
+ jars.addAll(context.getMetaData().getContainerResources());
jars.addAll(context.getMetaData().getWebInfJars());
JarScanner scanner = new JarScanner()
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
index a5053ed77c0..96f1c5fc7ab 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
@@ -85,7 +85,7 @@ public class WebInfConfiguration extends AbstractConfiguration
{
public void matched(URI uri) throws Exception
{
- context.getMetaData().addContainerJar(Resource.newResource(uri));
+ context.getMetaData().addContainerResource(Resource.newResource(uri));
}
};
ClassLoader loader = context.getClassLoader();
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
index 5d6cc1a6538..07b16dae9c4 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
@@ -86,7 +87,7 @@ public class UpgradeRequest
public String getHeader(String name)
{
- List values = headers.get(name);
+ List values = headers.get(name.toLowerCase(Locale.ENGLISH));
// no value list
if (values == null)
{
@@ -120,7 +121,7 @@ public class UpgradeRequest
public int getHeaderInt(String name)
{
- List values = headers.get(name);
+ List values = headers.get(name.toLowerCase(Locale.ENGLISH));
// no value list
if (values == null)
{
@@ -190,6 +191,13 @@ public class UpgradeRequest
return requestURI;
}
+ /**
+ * Access the Servlet HTTP Session (if present)
+ *
+ * Note: Never present on a Client UpgradeRequest.
+ *
+ * @return the Servlet HTTPSession on server side UpgradeRequests
+ */
public Object getSession()
{
return session;
@@ -224,14 +232,14 @@ public class UpgradeRequest
public void setHeader(String name, List values)
{
- headers.put(name,values);
+ headers.put(name.toLowerCase(Locale.ENGLISH),values);
}
public void setHeader(String name, String value)
{
List values = new ArrayList<>();
values.add(value);
- setHeader(name,values);
+ setHeader(name.toLowerCase(Locale.ENGLISH),values);
}
public void setHttpVersion(String httpVersion)
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java
new file mode 100644
index 00000000000..cb3af122996
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Exception thrown to indicate a connection I/O timeout.
+ */
+public class WebSocketTimeoutException extends WebSocketException
+{
+ private static final long serialVersionUID = -6145098200250676673L;
+
+ public WebSocketTimeoutException(String message)
+ {
+ super(message);
+ }
+
+ public WebSocketTimeoutException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public WebSocketTimeoutException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
index 2fb64761ce7..2ea7e09dd20 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
@@ -25,6 +25,7 @@ import org.eclipse.jetty.websocket.api.WebSocketException;
*/
public interface IncomingFrames
{
+ // TODO: JSR-356 change to Throwable
public void incomingError(WebSocketException e);
public void incomingFrame(Frame frame);
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
index fa4b1127c6c..8abdfff1d69 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
@@ -100,7 +100,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override
public void close() throws IOException
{
- connection.close();
+ this.close(StatusCode.NORMAL,null);
}
@Override
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
index 8f5310a6f1c..f3dffcbb2c4 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
@@ -46,6 +46,7 @@ import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -94,6 +95,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
// Abnormal Close
reason = CloseStatus.trimMaxReasonLength(reason);
+ session.incomingError(new WebSocketException(x)); // TODO: JSR-356 change to Throwable
session.notifyClose(StatusCode.NO_CLOSE,reason);
disconnect(); // disconnect endpoint & connection
@@ -102,7 +104,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public void succeeded()
{
- // Lets process the next set of bytes...
AbstractWebSocketConnection.this.complete(writeBytes);
}
}
@@ -286,11 +287,10 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
}
}
CloseInfo close = new CloseInfo(statusCode,reason);
- // TODO: create DisconnectCallback?
outgoingFrame(close.asFrame(),new OnCloseCallback());
}
- private void execute(Runnable task)
+ protected void execute(Runnable task)
{
try
{
@@ -502,6 +502,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
// Initiate close - politely send close frame.
// Note: it is not possible in 100% of cases during read timeout to send this close frame.
+ session.incomingError(new WebSocketTimeoutException("Timeout on Read"));
session.close(StatusCode.NORMAL,"Idle Timeout");
// Force closure of writeBytes
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
index 037a6d18283..e34e31b695d 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
@@ -165,10 +165,13 @@ public class WriteBytesProvider implements Callback
{
synchronized (this)
{
+ boolean notified = false;
+
// fail active (if set)
if (active != null)
{
active.notifyFailure(t);
+ notified = true;
}
failure = t;
@@ -177,12 +180,16 @@ public class WriteBytesProvider implements Callback
for (FrameEntry fe : queue)
{
fe.notifyFailure(t);
+ notified = true;
}
queue.clear();
- // notify flush callback
- flushCallback.failed(t);
+ if (notified)
+ {
+ // notify flush callback
+ flushCallback.failed(t);
+ }
}
}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java
index 46e021aba66..ad24b23d94b 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java
@@ -106,11 +106,6 @@ public class ServletWebSocketRequest extends UpgradeRequest
return req.getUserPrincipal();
}
- public StringBuffer getRequestURL()
- {
- return req.getRequestURL();
- }
-
public Map getServletAttributes()
{
Map attributes = new HashMap();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
new file mode 100644
index 00000000000..21ffcaeda66
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
@@ -0,0 +1,99 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.RFCSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class IdleTimeoutTest
+{
+ @SuppressWarnings("serial")
+ public static class TimeoutServlet extends WebSocketServlet
+ {
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ factory.getPolicy().setIdleTimeout(500);
+ factory.register(RFCSocket.class);
+ }
+ }
+
+ private static SimpleServletServer server;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new SimpleServletServer(new TimeoutServlet());
+ server.start();
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ server.stop();
+ }
+
+ /**
+ * Test IdleTimeout on server.
+ */
+ @Test
+ public void testIdleTimeout() throws Exception
+ {
+ BlockheadClient client = new BlockheadClient(server.getServerUri());
+ client.setProtocols("onConnect");
+ client.setTimeout(TimeUnit.MILLISECONDS,1500);
+ try
+ {
+ client.connect();
+ client.sendStandardRequest();
+ client.expectUpgradeResponse();
+
+ // This wait should be shorter than client timeout above, but
+ // longer than server timeout configured in TimeoutServlet
+ client.sleep(TimeUnit.MILLISECONDS,1000);
+
+ // Write to server (the server should be timed out and disconnect now)
+ client.write(WebSocketFrame.text("Hello"));
+
+ // now attempt to read 2 echoed frames from server (shouldn't work)
+ client.readFrames(2,TimeUnit.MILLISECONDS,1500);
+ Assert.fail("Should have resulted in IOException");
+ }
+ catch (IOException e)
+ {
+ Assert.assertThat("IOException",e.getMessage(),anyOf(containsString("closed"),containsString("disconnected")));
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
new file mode 100644
index 00000000000..cb2770d24d6
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
@@ -0,0 +1,161 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.server.helper.RFCSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests various close scenarios
+ */
+public class WebSocketCloseTest
+{
+ @SuppressWarnings("serial")
+ public static class CloseServlet extends WebSocketServlet implements WebSocketCreator
+ {
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ factory.setCreator(this);
+ }
+
+ @Override
+ public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
+ {
+ if (req.hasSubProtocol("fastclose"))
+ {
+ fastcloseSocket = new FastCloseSocket();
+ return fastcloseSocket;
+ }
+ return new RFCSocket();
+ }
+ }
+
+ public static class FastCloseSocket extends WebSocketAdapter
+ {
+ public CountDownLatch closeLatch = new CountDownLatch(1);
+ public String closeReason = null;
+ public int closeStatusCode = -1;
+ public List errors = new ArrayList<>();
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason)
+ {
+ LOG.debug("onWebSocketClose({}, {})",statusCode,reason);
+ this.closeStatusCode = statusCode;
+ this.closeReason = reason;
+ closeLatch.countDown();
+ }
+
+ @Override
+ public void onWebSocketConnect(Session sess)
+ {
+ LOG.debug("onWebSocketConnect({})",sess);
+ try
+ {
+ sess.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ @Override
+ public void onWebSocketError(Throwable cause)
+ {
+ errors.add(cause);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(WebSocketCloseTest.class);
+ private static SimpleServletServer server;
+ private static FastCloseSocket fastcloseSocket;
+
+ @BeforeClass
+ public static void startServer() throws Exception
+ {
+ server = new SimpleServletServer(new CloseServlet());
+ server.start();
+ }
+
+ @AfterClass
+ public static void stopServer()
+ {
+ server.stop();
+ }
+
+ /**
+ * Test fast close (bug #403817)
+ */
+ @Test
+ public void testFastClose() throws Exception
+ {
+ BlockheadClient client = new BlockheadClient(server.getServerUri());
+ client.setProtocols("fastclose");
+ client.setTimeout(TimeUnit.SECONDS,1);
+ try
+ {
+ client.connect();
+ client.sendStandardRequest();
+ client.expectUpgradeResponse();
+
+ IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
+ WebSocketFrame frame = capture.getFrames().get(0);
+ Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+ CloseInfo close = new CloseInfo(frame);
+ Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
+
+ client.write(close.asFrame()); // respond with close
+
+ Assert.assertThat("Fast Close Latch",fastcloseSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+ Assert.assertThat("Fast Close.statusCode",fastcloseSocket.closeStatusCode,is(StatusCode.NORMAL));
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
index fe67a19d8d6..294ee654953 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
@@ -190,35 +190,6 @@ public class WebSocketServletRFCTest
}
}
- @Test
- @Ignore("Still not working")
- public void testIdle() throws Exception
- {
- BlockheadClient client = new BlockheadClient(server.getServerUri());
- client.setProtocols("onConnect");
- client.setTimeout(TimeUnit.MILLISECONDS,800);
- try
- {
- client.connect();
- client.sendStandardRequest();
- client.expectUpgradeResponse();
-
- client.sleep(TimeUnit.SECONDS,1);
-
- client.write(WebSocketFrame.text("Hello"));
-
- // now wait for the server to time out
- // should be 2 frames, the TextFrame echo, and then the Close on disconnect
- IncomingFramesCapture capture = client.readFrames(2,TimeUnit.SECONDS,2);
- Assert.assertThat("frames[0].opcode",capture.getFrames().get(0).getOpCode(),is(OpCode.TEXT));
- Assert.assertThat("frames[1].opcode",capture.getFrames().get(1).getOpCode(),is(OpCode.CLOSE));
- }
- finally
- {
- client.close();
- }
- }
-
/**
* Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal server error) being produced by the
* WebSocket POJO.
@@ -248,6 +219,48 @@ public class WebSocketServletRFCTest
}
}
+ /**
+ * Test http://tools.ietf.org/html/rfc6455#section-4.1 where server side upgrade handling is supposed to be case insensitive.
+ *
+ * This test will simulate a client requesting upgrade with all lowercase headers.
+ */
+ @Test
+ public void testLowercaseUpgrade() throws Exception
+ {
+ BlockheadClient client = new BlockheadClient(server.getServerUri());
+ try
+ {
+ client.connect();
+
+ StringBuilder req = new StringBuilder();
+ req.append("GET ").append(client.getRequestPath()).append(" HTTP/1.1\r\n");
+ req.append("Host: ").append(client.getRequestHost()).append("\r\n");
+ req.append("Upgrade: websocket\r\n");
+ req.append("connection: upgrade\r\n");
+ req.append("sec-websocket-key: ").append(client.getRequestWebSocketKey()).append("\r\n");
+ req.append("sec-websocket-origin: ").append(client.getRequestWebSocketOrigin()).append("\r\n");
+ req.append("sec-websocket-protocol: echo\r\n");
+ req.append("sec-websocket-version: 13\r\n");
+ req.append("\r\n");
+ client.writeRaw(req.toString());
+
+ client.expectUpgradeResponse();
+
+ // Generate text frame
+ String msg = "this is an echo ... cho ... ho ... o";
+ client.write(WebSocketFrame.text(msg));
+
+ // Read frame (hopefully text frame)
+ IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
+ WebSocketFrame tf = capture.getFrames().get(0);
+ Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+
@Test
@Ignore("Should be moved to Fuzzer")
public void testMaxBinarySize() throws Exception
@@ -366,4 +379,46 @@ public class WebSocketServletRFCTest
client.close();
}
}
+
+ /**
+ * Test http://tools.ietf.org/html/rfc6455#section-4.1 where server side upgrade handling is supposed to be case insensitive.
+ *
+ * This test will simulate a client requesting upgrade with all uppercase headers.
+ */
+ @Test
+ public void testUppercaseUpgrade() throws Exception
+ {
+ BlockheadClient client = new BlockheadClient(server.getServerUri());
+ try
+ {
+ client.connect();
+
+ StringBuilder req = new StringBuilder();
+ req.append("GET ").append(client.getRequestPath()).append(" HTTP/1.1\r\n");
+ req.append("HOST: ").append(client.getRequestHost()).append("\r\n");
+ req.append("UPGRADE: WEBSOCKET\r\n");
+ req.append("CONNECTION: UPGRADE\r\n");
+ req.append("SEC-WEBSOCKET-KEY: ").append(client.getRequestWebSocketKey()).append("\r\n");
+ req.append("SEC-WEBSOCKET-ORIGIN: ").append(client.getRequestWebSocketOrigin()).append("\r\n");
+ req.append("SEC-WEBSOCKET-PROTOCOL: ECHO\r\n");
+ req.append("SEC-WEBSOCKET-VERSION: 13\r\n");
+ req.append("\r\n");
+ client.writeRaw(req.toString());
+
+ client.expectUpgradeResponse();
+
+ // Generate text frame
+ String msg = "this is an echo ... cho ... ho ... o";
+ client.write(WebSocketFrame.text(msg));
+
+ // Read frame (hopefully text frame)
+ IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
+ WebSocketFrame tf = capture.getFrames().get(0);
+ Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+ }
+ finally
+ {
+ client.close();
+ }
+ }
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
index 9eea24986f9..561f9a89c50 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
@@ -29,7 +29,9 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
@@ -202,10 +204,14 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
{
InetAddress destAddr = InetAddress.getByName(destHttpURI.getHost());
int port = destHttpURI.getPort();
- socket = new Socket(destAddr,port);
+
+ SocketAddress endpoint = new InetSocketAddress(destAddr,port);
+
+ socket = new Socket();
+ socket.setSoTimeout(timeout);
+ socket.connect(endpoint);
out = socket.getOutputStream();
- socket.setSoTimeout(timeout);
in = socket.getInputStream();
}
@@ -330,6 +336,39 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
return protocols;
}
+ public String getRequestHost()
+ {
+ if (destHttpURI.getPort() > 0)
+ {
+ return String.format("%s:%d",destHttpURI.getHost(),destHttpURI.getPort());
+ }
+ else
+ {
+ return destHttpURI.getHost();
+ }
+ }
+
+ public String getRequestPath()
+ {
+ StringBuilder path = new StringBuilder();
+ path.append(destHttpURI.getPath());
+ if (StringUtil.isNotBlank(destHttpURI.getQuery()))
+ {
+ path.append('?').append(destHttpURI.getQuery());
+ }
+ return path.toString();
+ }
+
+ public String getRequestWebSocketKey()
+ {
+ return REQUEST_HASH_KEY;
+ }
+
+ public String getRequestWebSocketOrigin()
+ {
+ return destWebsocketURI.toASCIIString();
+ }
+
public int getVersion()
{
return version;
@@ -557,27 +596,16 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
public void sendStandardRequest() throws IOException
{
StringBuilder req = new StringBuilder();
- req.append("GET ");
- req.append(destHttpURI.getPath());
- if (StringUtil.isNotBlank(destHttpURI.getQuery()))
- {
- req.append('?').append(destHttpURI.getQuery());
- }
- req.append(" HTTP/1.1\r\n");
- req.append("Host: ").append(destHttpURI.getHost());
- if (destHttpURI.getPort() > 0)
- {
- req.append(':').append(destHttpURI.getPort());
- }
- req.append("\r\n");
+ req.append("GET ").append(getRequestPath()).append(" HTTP/1.1\r\n");
+ req.append("Host: ").append(getRequestHost()).append("\r\n");
req.append("Upgrade: websocket\r\n");
req.append("Connection: Upgrade\r\n");
for (String header : headers)
{
req.append(header);
}
- req.append("Sec-WebSocket-Key: ").append(REQUEST_HASH_KEY).append("\r\n");
- req.append("Sec-WebSocket-Origin: ").append(destWebsocketURI.toASCIIString()).append("\r\n");
+ req.append("Sec-WebSocket-Key: ").append(getRequestWebSocketKey()).append("\r\n");
+ req.append("Sec-WebSocket-Origin: ").append(getRequestWebSocketOrigin()).append("\r\n");
if (StringUtil.isNotBlank(protocols))
{
req.append("Sec-WebSocket-Protocol: ").append(protocols).append("\r\n");
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 53c24582038..467f5d77bd8 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
@@ -1195,16 +1195,19 @@ public class XmlConfiguration
}
}
- // For all arguments, load properties or parse XMLs
+ // For all arguments, load properties
+ for (int i = 0; i < args.length; i++)
+ {
+ if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
+ properties.load(Resource.newResource(args[i]).getInputStream());
+ }
+
+ // For all arguments, parse XMLs
XmlConfiguration last = null;
Object[] obj = new Object[args.length];
for (int i = 0; i < args.length; i++)
{
- if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
- {
- properties.load(Resource.newResource(args[i]).getInputStream());
- }
- else
+ if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
{
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
if (last != null)
diff --git a/pom.xml b/pom.xml
index 82d1e592bbc..36e9d6ed07b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -267,7 +267,7 @@
org.eclipse.jetty.toolchain
jetty-version-maven-plugin
- 1.0.9
+ 1.0.10
org.apache.maven.plugins
@@ -452,6 +452,7 @@
tests
jetty-distribution
jetty-runner
+ jetty-monitor