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 entry : response.entrySet()) + { + String[] values = ((String) entry.getValue()).split("\\|"); + + queryString = values[0]; + if (queryString.startsWith("com.javamonitor.openfire")) + continue; + + if (queryString.startsWith("com.javamonitor")) + { + queryString = "org.eclipse.jetty.monitor.integration:type=javamonitortools,id=0"; + } + + queryResults = null; + try + { + queryResults = queryNames(queryString); + } + catch (IOException e) + { + LOG.debug(e); + } + catch (MalformedObjectNameException e) + { + LOG.debug(e); + } + + if (queryResults != null) + { + int idx = 0; + for(ObjectName objName : queryResults) + { + String id = entry.getKey().toString()+(idx == 0 ? "" : ":"+idx); + String name = queryString.equals(objName.toString()) ? "" : objName.toString(); + boolean repeat = Boolean.parseBoolean(values[2]); + trigger.add(new JavaMonitorTrigger(objName, values[1], id, name, repeat)); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @param value + * @return + */ + private int getClassID(final Object value) + { + if (value == null) + return 0; + + if (value instanceof Byte || + value instanceof Short || + value instanceof Integer || + value instanceof Long) + return 1; + + if (value instanceof Float || + value instanceof Double) + return 2; + + if (value instanceof Boolean) + return 3; + + return 4; // String + } + + /* ------------------------------------------------------------ */ + /** + * @return + * @throws Exception + */ + private String getServerIP() + throws Exception + { + Socket s = null; + try { + if (getProperty("http.proxyHost") != null) + { + s = new Socket(getProperty("http.proxyHost"), + parseInt(getProperty("http.proxyPort", "80"))); + } + else + { + int port = 80; + + URL url = new URL(_url); + if (url.getPort() != -1) { + port = url.getPort(); + } + s = new Socket(url.getHost(), port); + } + return s.getLocalAddress().getHostAddress(); + } + finally + { + try + { + if (s != null) + s.close(); + } + catch (IOException ex) + { + LOG.ignore(ex); + } + } + } + + /* ------------------------------------------------------------ */ + public Integer getHttpPort() + { + Collection connectors = null; + MBeanServerConnection service; + try + { + service = JMXMonitor.getServiceConnection(); + + connectors = service.queryNames(new ObjectName("org.eclipse.jetty.nio:type=selectchannelconnector,*"), null); + if (connectors != null && connectors.size() > 0) + { + Integer lowest = Integer.MAX_VALUE; + for (final ObjectName connector : connectors) { + lowest = (Integer)service.getAttribute(connector, "port"); + } + + if (lowest < Integer.MAX_VALUE) + return lowest; + } + } + catch (Exception ex) + { + LOG.debug(ex); + } + + return 0; + } + + /* ------------------------------------------------------------ */ + /** + * @param param + * @return + * @throws IOException + * @throws NullPointerException + * @throws MalformedObjectNameException + */ + private ObjectName[] queryNames(ObjectName param) + throws IOException, MalformedObjectNameException + { + ObjectName[] result = null; + + MBeanServerConnection connection = JMXMonitor.getServiceConnection(); + Set names = connection.queryNames(param, null); + if (names != null && names.size() > 0) + { + result = new ObjectName[names.size()]; + + int idx = 0; + for(Object name : names) + { + if (name instanceof ObjectName) + result[idx++] = (ObjectName)name; + else + result[idx++] = new ObjectName(name.toString()); + } + } + + return result; + } + + private ObjectName[] queryNames(String param) + throws IOException, MalformedObjectNameException + { + return queryNames(new ObjectName(param)); + } + + } diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java new file mode 100644 index 00000000000..4aa69be96d9 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java @@ -0,0 +1,289 @@ +// +// ======================================================================== +// 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 java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.annotation.ManagedOperation; + +/* ------------------------------------------------------------ */ +/** + * Derived from the JMX bean classes created by Kees Jan Koster for the java-monitor + * J2EE probe http://code.google.com/p/java-monitor-probes/source/browse/. + * + * @author kjkoster + */ +@ManagedObject("Java Monitoring Tools") +public class JavaMonitorTools +{ + private static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + + private static Method findDeadlockMethod = null; + + static + { + try + { + findDeadlockMethod = ThreadMXBean.class.getMethod("findDeadlockedThreads"); + } + catch (Exception ignored) + { + // this is a 1.5 JVM + try + { + findDeadlockMethod = ThreadMXBean.class.getMethod("findMonitorDeadlockedThreads"); + } + catch (SecurityException e) + { + e.printStackTrace(); + } + catch (NoSuchMethodException e) + { + e.printStackTrace(); + } + } + } + + private ThreadInfo[] findDeadlock() + throws IllegalAccessException, InvocationTargetException + { + final long[] threadIds = (long[])findDeadlockMethod.invoke(threadMXBean,(Object[])null); + + if (threadIds == null || threadIds.length < 1) + { + // no deadlock, we're done + return null; + } + + final ThreadInfo[] threads = threadMXBean.getThreadInfo(threadIds,Integer.MAX_VALUE); + return threads; + } + @ManagedOperation(value="Detailed report on the deadlocked threads.", impact="ACTION_INFO") + public String getDeadlockStacktraces() + { + try + { + final ThreadInfo[] threads = findDeadlock(); + if (threads == null) + { + // no deadlock, we're done + return null; + } + + return stacktraces(threads,0); + } + catch (Exception e) + { + return e.getMessage(); + } + } + + private static final int MAX_STACK = 10; + + private String stacktraces(final ThreadInfo[] threads, final int i) + { + if (i >= threads.length) + { + return ""; + } + final ThreadInfo thread = threads[i]; + + final StringBuilder trace = new StringBuilder(); + for (int stack_i = 0; stack_i < Math.min(thread.getStackTrace().length,MAX_STACK); stack_i++) + { + if (stack_i == (MAX_STACK - 1)) + { + trace.append(" ..."); + } + else + { + trace.append(" at ").append(thread.getStackTrace()[stack_i]).append("\n"); + } + } + + return "\"" + thread.getThreadName() + "\", id " + thread.getThreadId() + " is " + thread.getThreadState() + " on " + thread.getLockName() + + ", owned by " + thread.getLockOwnerName() + ", id " + thread.getLockOwnerId() + "\n" + trace + "\n\n" + stacktraces(threads,i + 1); + } + + /** + * We keep track of the last time we sampled the thread states. + * It is a crude optimization to avoid having to query for the + * threads states very often. + */ + private long lastSampled = 0L; + + private final Map states = new HashMap(); + + @ManagedOperation(value="Number of Blocked Threads") + public int getThreadsBlocked() + { + sampleThreads(); + + return states.get(Thread.State.BLOCKED); + } + + @ManagedOperation(value="Number of New Threads", impact="ACTION_INFO") + public int getThreadsNew() + { + sampleThreads(); + + return states.get(Thread.State.NEW); + } + + @ManagedOperation(value="Number of Terminated Threads", impact="ACTION_INFO") + public int getThreadsTerminated() + { + sampleThreads(); + + return states.get(Thread.State.TERMINATED); + } + + @ManagedOperation(value="Number of Sleeping and Waiting threads") + public int getThreadsTimedWaiting() + { + sampleThreads(); + + return states.get(Thread.State.TIMED_WAITING); + } + + @ManagedOperation(value="Number of Waiting Threads", impact="ACTION_INFO") + public int getThreadsWaiting() + { + sampleThreads(); + + return states.get(Thread.State.WAITING); + } + + @ManagedOperation(value="Number of Runnable Threads", impact="ACTION_INFO") + public int getThreadsRunnable() + { + sampleThreads(); + + return states.get(Thread.State.RUNNABLE); + } + + private synchronized void sampleThreads() + { + if ((lastSampled + 50L) < System.currentTimeMillis()) + { + lastSampled = System.currentTimeMillis(); + for (final Thread.State state : Thread.State.values()) + { + states.put(state,0); + } + + for (final ThreadInfo thread : threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds())) + { + if (thread != null) + { + final Thread.State state = thread.getThreadState(); + states.put(state,states.get(state) + 1); + } + else + { + states.put(Thread.State.TERMINATED,states.get(Thread.State.TERMINATED) + 1); + } + } + } + } + + private static final String POLICY = "sun.net.InetAddressCachePolicy"; + + @ManagedOperation(value="Amount of time successful DNS queries are cached for.") + public int getCacheSeconds() throws ClassNotFoundException, + IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + final Class policy = Class.forName(POLICY); + final Object returnValue = policy.getMethod("get", (Class[]) null) + .invoke(null, (Object[]) null); + Integer seconds = (Integer) returnValue; + + return seconds.intValue(); + } + + @ManagedOperation(value="Amount of time failed DNS queries are cached for") + public int getCacheNegativeSeconds() throws ClassNotFoundException, + IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + final Class policy = Class.forName(POLICY); + final Object returnValue = policy.getMethod("getNegative", + (Class[]) null).invoke(null, (Object[]) null); + Integer seconds = (Integer) returnValue; + + return seconds.intValue(); + } + + private static final String DEFAULT = "default"; + + private static final String SECURITY = "security"; + + private static final String SYSTEM = "system"; + + private static final String BOTH = "both"; + + private static final String SECURITY_TTL = "networkaddress.cache.ttl"; + + private static final String SYSTEM_TTL = "sun.net.inetaddr.ttl"; + + private static final String SECURITY_NEGATIVE_TTL = "networkaddress.cache.negative.ttl"; + + private static final String SYSTEM_NEGATIVE_TTL = "sun.net.inetaddr.negative.ttl"; + + @ManagedOperation(value="Cache policy for successful DNS lookups was changed from the hard-coded default") + public String getCacheTweakedFrom() { + if (Security.getProperty(SECURITY_TTL) != null) { + if (System.getProperty(SYSTEM_TTL) != null) { + return BOTH; + } + + return SECURITY; + } + + if (System.getProperty(SYSTEM_TTL) != null) { + return SYSTEM; + } + + return DEFAULT; + } + + @ManagedOperation(value="Cache policy for failed DNS lookups was changed from the hard-coded default") + public String getCacheNegativeTweakedFrom() { + if (Security.getProperty(SECURITY_NEGATIVE_TTL) != null) { + if (System.getProperty(SYSTEM_NEGATIVE_TTL) != null) { + return BOTH; + } + + return SECURITY; + } + + if (System.getProperty(SYSTEM_NEGATIVE_TTL) != null) { + return SYSTEM; + } + + return DEFAULT; + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java new file mode 100644 index 00000000000..8c8abe4c772 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java @@ -0,0 +1,81 @@ +// +// ======================================================================== +// 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 javax.management.ObjectName; + +import org.eclipse.jetty.monitor.triggers.AttrEventTrigger; + + +/* ------------------------------------------------------------ */ +/** + */ +public class JavaMonitorTrigger > + extends AttrEventTrigger +{ + private final String _id; + private final String _name; + private final boolean _dynamic; + private int _count; + + /* ------------------------------------------------------------ */ + /** + * @param nameObject + * @param attributeName + * @param id + * @param dynamic + * @throws IllegalArgumentException + */ + public JavaMonitorTrigger(ObjectName nameObject, String attributeName, String id, String name, boolean dynamic) + throws IllegalArgumentException + { + super(nameObject, attributeName); + + _id = id; + _name = name; + _dynamic = dynamic; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return _dynamic ? true : (_count++ < 1); + } + + protected boolean getSaveAll() + { + return false; + } + + @Override + public String getID() + { + return _id; + } + + @Override + public String getNameString() + { + return _name; + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ConsoleNotifier.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ConsoleNotifier.java new file mode 100644 index 00000000000..5452bf574cd --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ConsoleNotifier.java @@ -0,0 +1,61 @@ +// +// ======================================================================== +// 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.jmx; + + +/* ------------------------------------------------------------ */ +/** + * ConsoleNotifier + * + * Provides a way to output notification messages to the server console + */ +public class ConsoleNotifier implements EventNotifier +{ + String _messageFormat; + + + /* ------------------------------------------------------------ */ + /** + * Constructs a new notifier with specified format string + * + * @param format the {@link java.util.Formatter format string} + * @throws IllegalArgumentException + */ + public ConsoleNotifier(String format) + throws IllegalArgumentException + { + if (format == null) + throw new IllegalArgumentException("Message format cannot be null"); + + _messageFormat = format; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.monitor.jmx.EventNotifier#notify(org.eclipse.jetty.monitor.jmx.EventTrigger, org.eclipse.jetty.monitor.jmx.EventState, long) + */ + public void notify(EventTrigger trigger, EventState state, long timestamp) + { + String output = String.format("%1$tF %1$tT.%1$tL:NOTIFY::", timestamp); + + output += String.format(_messageFormat, state); + + System.out.println(output); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventNotifier.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventNotifier.java new file mode 100644 index 00000000000..c728937e707 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventNotifier.java @@ -0,0 +1,39 @@ +// +// ======================================================================== +// 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.jmx; + + +/* ------------------------------------------------------------ */ +/** + * EventNotifier + * + * Interface for classes used to send event notifications + */ +public interface EventNotifier +{ + + /* ------------------------------------------------------------ */ + /** + * This method is called when a notification event is received by the containing object + * + * @param state an {@link org.eclipse.jetty.monitor.jmx.EventState event state} + * @param timestamp time stamp of the event + */ + public void notify(EventTrigger trigger, EventState state, long timestamp); +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventState.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventState.java new file mode 100644 index 00000000000..3151d212a42 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventState.java @@ -0,0 +1,207 @@ +// +// ======================================================================== +// 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.jmx; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +/* ------------------------------------------------------------ */ +/** + * EventState + * + * Holds the state of one or more {@link org.eclipse.jetty.monitor.jmx.EventTrigger event trigger} + * instances to be used when sending notifications as well as executing the actions + */ +public class EventState +{ + + /* ------------------------------------------------------------ */ + /** + * State + * + * Holds the state of a single {@link org.eclipse.jetty.monitor.jmx.EventTrigger event trigger} + */ + public static class TriggerState + { + private final String _id; + private final String _desc; + private final TYPE _value; + + /* ------------------------------------------------------------ */ + /** + * Construct a trigger state + * + * @param id unique identification string of the associated event trigger + * @param desc description of the associated event trigger + * @param value effective value of the MXBean attribute (if applicable) + */ + public TriggerState(String id, String desc, TYPE value) + { + _id = id; + _desc = desc; + _value = value; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the identification string of associated event trigger + * + * @return unique identification string + */ + public String getID() + { + return _id; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the description string set by event trigger + * + * @return description string + */ + public String getDescription() + { + return _desc; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the effective value of the MXBean attribute (if applicable) + * + * @return attribute value + */ + public TYPE getValue() + { + return _value; + } + + /* ------------------------------------------------------------ */ + /** + * @return string representation of the state + */ + public String toString() + { + StringBuilder result = new StringBuilder(); + + result.append(_desc); + result.append('='); + result.append(_value); + + return result.toString(); + } + } + + protected Map> _states; + + /* ------------------------------------------------------------ */ + /** + * Constructs an empty event state + */ + public EventState() + { + _states = new ConcurrentHashMap>(); + } + + + /* ------------------------------------------------------------ */ + /** + * Constructs an event state and adds a specified trigger state to it + * + * @param id unique identification string of the associated event trigger + * @param desc description of the associated event trigger + * @param value effective value of the MXBean attribute (if applicable) + */ + public EventState(String id, String desc, TYPE value) + { + this(); + + add(new TriggerState(id, desc, value)); + } + + /* ------------------------------------------------------------ */ + /** + * Adds a trigger state to the event state + * + * @param state trigger state to add + */ + public void add(TriggerState state) + { + _states.put(state.getID(), state); + } + + /* ------------------------------------------------------------ */ + /** + * Adds a collection of trigger states to the event state + * + * @param entries collection of trigger states to add + */ + public void addAll(Collection> entries) + { + for (TriggerState entry : entries) + { + add(entry); + } + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves a single trigger state + * + * @param id unique identification string of the event trigger + * @return requested trigger state or null if not found + */ + public TriggerState get(String id) + { + return _states.get(id); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves a collection of all trigger states of the event state + * + * @return collection of the trigger states + */ + public Collection> values() + { + return Collections.unmodifiableCollection(_states.values()); + } + + /* ------------------------------------------------------------ */ + /** + * Returns a string representation of the event state + * + * @return string representation of the event state + */ + public String toString() + { + int cnt = 0; + StringBuilder result = new StringBuilder(); + + for (TriggerState value : _states.values()) + { + result.append(cnt++>0?"#":""); + result.append(value.toString()); + } + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventTrigger.java new file mode 100644 index 00000000000..a49420a2af5 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventTrigger.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// 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.jmx; + +import static java.util.UUID.randomUUID; + +/* ------------------------------------------------------------ */ +/** + * EventTrigger + * + * Abstract base class for all EventTrigger implementations. + * Used to determine whether the necessary conditions for + * triggering an event are present. + */ +public abstract class EventTrigger +{ + private final String _id; + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger + */ + public EventTrigger() + { + _id = randomUUID().toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the identification string of the event trigger + * + * @return unique identification string + */ + public String getID() + { + return _id; + } + + /* ------------------------------------------------------------ */ + /** + * Abstract method to verify if the event trigger conditions + * are in the appropriate state for an event to be triggered + * + * @return true to trigger an event + */ + public abstract boolean match(long timestamp) throws Exception; + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event state associated with specified invocation + * of the event trigger match method + * + * @param timestamp time stamp associated with invocation + * @return event state or null if not found + */ + public abstract EventState getState(long timestamp); +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/LoggingNotifier.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/LoggingNotifier.java new file mode 100644 index 00000000000..b8484f4889b --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/LoggingNotifier.java @@ -0,0 +1,65 @@ +// +// ======================================================================== +// 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.jmx; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * ConsoleNotifier + * + * Provides a way to output notification messages to a log file + */ +public class LoggingNotifier implements EventNotifier +{ + private static final Logger LOG = Log.getLogger(LoggingNotifier.class); + + String _messageFormat; + + /* ------------------------------------------------------------ */ + /** + * Constructs a new notifier with specified format string + * + * @param format the {@link java.util.Formatter format string} + * @throws IllegalArgumentException + */ + public LoggingNotifier(String format) + throws IllegalArgumentException + { + if (format == null) + throw new IllegalArgumentException("Message format cannot be null"); + + _messageFormat = format; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.monitor.jmx.EventNotifier#notify(org.eclipse.jetty.monitor.jmx.EventTrigger, org.eclipse.jetty.monitor.jmx.EventState, long) + */ + public void notify(EventTrigger trigger, EventState state, long timestamp) + { + String output = String.format(_messageFormat, state); + + LOG.info(output); + } + +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorAction.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorAction.java new file mode 100644 index 00000000000..32ff52316e4 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorAction.java @@ -0,0 +1,179 @@ +// +// ======================================================================== +// 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.jmx; + +import static java.util.UUID.randomUUID; + +import java.security.InvalidParameterException; + +/* ------------------------------------------------------------ */ +/** + * MonitorAction + * + * Abstract base class for all MonitorAction implementations. + * Receives notification when an associated EventTrigger is matched. + */ +public abstract class MonitorAction + extends NotifierGroup +{ + public static final int DEFAULT_POLL_INTERVAL = 5000; + + private final String _id; + private final EventTrigger _trigger; + private final EventNotifier _notifier; + private final long _pollInterval; + private final long _pollDelay; + + /* ------------------------------------------------------------ */ + /** + * Creates a new monitor action + * + * @param trigger event trigger to be associated with this action + * @throws InvalidParameterException + */ + public MonitorAction(EventTrigger trigger) + throws InvalidParameterException + { + this(trigger, null, 0, 0); + } + + + /* ------------------------------------------------------------ */ + /** + * Creates a new monitor action + * + * @param trigger event trigger to be associated with this action + * @param notifier event notifier to be associated with this action + * @throws InvalidParameterException + */ + public MonitorAction(EventTrigger trigger, EventNotifier notifier) + throws InvalidParameterException + { + this(trigger, notifier, 0); + } + + /* ------------------------------------------------------------ */ + /** + * Creates a new monitor action + * + * @param trigger event trigger to be associated with this action + * @param notifier event notifier to be associated with this action + * @param pollInterval interval for polling of the JMX server + * @throws InvalidParameterException + */ + public MonitorAction(EventTrigger trigger, EventNotifier notifier, long pollInterval) + throws InvalidParameterException + { + this(trigger, notifier, pollInterval, 0); + } + + /* ------------------------------------------------------------ */ + /** + * Creates a new monitor action + * + * @param trigger event trigger to be associated with this action + * @param notifier event notifier to be associated with this action + * @param pollInterval interval for polling of the JMX server + * @param pollDelay delay before starting to poll the JMX server + * @throws InvalidParameterException + */ + public MonitorAction(EventTrigger trigger, EventNotifier notifier, long pollInterval, long pollDelay) + throws InvalidParameterException + { + if (trigger == null) + throw new InvalidParameterException("Trigger cannot be null"); + + _id = randomUUID().toString(); + _trigger = trigger; + _notifier = notifier; + _pollInterval = pollInterval > 0 ? pollInterval : DEFAULT_POLL_INTERVAL; + _pollDelay = pollDelay > 0 ? pollDelay : _pollInterval; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the identification string of the monitor action + * + * @return unique identification string + */ + + public final String getID() + { + return _id; + } + + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event trigger of the monitor action + * + * @return associated event trigger + */ + public EventTrigger getTrigger() + { + return _trigger; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the poll interval + * + * @return interval value (in milliseconds) + */ + public long getPollInterval() + { + return _pollInterval; + } + + + /* ------------------------------------------------------------ */ + /** Retrieve the poll delay + * @return delay value (in milliseconds) + */ + public long getPollDelay() + { + return _pollDelay; + } + + /* ------------------------------------------------------------ */ + /** + * This method will be called when event trigger associated + * with this monitor action matches its conditions. + * + * @param timestamp time stamp of the event + */ + public final void doExecute(long timestamp) + { + EventState state =_trigger.getState(timestamp); + if (_notifier != null) + _notifier.notify(_trigger, state, timestamp); + execute(_trigger, state, timestamp); + } + + /* ------------------------------------------------------------ */ + /** + * This method will be called to allow subclass to execute + * the desired action in response to the event. + * + * @param trigger event trigger associated with this monitor action + * @param state event state associated with current invocation of event trigger + * @param timestamp time stamp of the current invocation of event trigger + */ + public abstract void execute(EventTrigger trigger, EventState state, long timestamp); + } diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java new file mode 100644 index 00000000000..f39553f2b5a --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java @@ -0,0 +1,119 @@ +// +// ======================================================================== +// 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.jmx; + +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.ExecutorThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; + +/* ------------------------------------------------------------ */ +/** + * MonitorTask + * + * Invokes polling of the JMX server for the MBean attribute values + * through executing timer task scheduled using java.util.Timer + * at specified poll interval following a specified delay. + */ +public class MonitorTask extends TimerTask +{ + private static final Logger LOG = Log.getLogger(MonitorTask.class); + + private static Timer __timer = new Timer(true); + private static ThreadPool _callback = new ExecutorThreadPool(4,64,60,TimeUnit.SECONDS);; + private static Map __tasks = new HashMap(); + + private final MonitorAction _action; + + /* ------------------------------------------------------------ */ + /** + * Creates new instance of MonitorTask + * + * @param action instance of MonitorAction to use + */ + private MonitorTask(MonitorAction action) + { + _action = action; + } + + /* ------------------------------------------------------------ */ + /** + * Schedule new timer task for specified monitor action + * + * @param action monitor action + */ + public static void schedule(MonitorAction action) + { + TimerTask task = new MonitorTask(action); + __timer.scheduleAtFixedRate(task, + action.getPollDelay(), + action.getPollInterval()); + + __tasks.put(action.getID(), task); + } + + /* ------------------------------------------------------------ */ + /** + * Cancel timer task for specified monitor action + * + * @param action monitor action + */ + public static void cancel(MonitorAction action) + { + TimerTask task = __tasks.remove(action.getID()); + if (task != null) + task.cancel(); + } + + /* ------------------------------------------------------------ */ + /** + * This method is invoked when poll interval has elapsed + * to check if the event trigger conditions are satisfied + * in order to fire event. + * + * @see java.util.TimerTask#run() + */ + @Override + public final void run() + { + final long timestamp = System.currentTimeMillis(); + final EventTrigger trigger = _action.getTrigger(); + + _callback.execute(new Runnable() { + public void run() + { + try + { + if(trigger.match(timestamp)) + _action.doExecute(timestamp); + } + catch (Exception ex) + { + LOG.debug(ex); + } + } + }); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java new file mode 100644 index 00000000000..8462a317954 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java @@ -0,0 +1,119 @@ +// +// ======================================================================== +// 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.jmx; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + + +/* ------------------------------------------------------------ */ +/** + * NotifierGroup + * + * This class allows for grouping of the event notifiers + */ +public class NotifierGroup implements EventNotifier +{ + private Set _group; + + /* ------------------------------------------------------------ */ + /** + * Create a notifier group + */ + public NotifierGroup() + { + _group = new HashSet(); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve all event notifier associated with this group + * + * @return collection of event notifiers + */ + public Collection getNotifiers() + { + return Collections.unmodifiableSet(_group); + } + + /* ------------------------------------------------------------ */ + /** + * Add specified event notifier to event notifier group + * + * @param notifier event notifier to be added + * @return true if successful + */ + public boolean addNotifier(EventNotifier notifier) + { + return _group.add(notifier); + } + + /* ------------------------------------------------------------ */ + /** + * Add a collection of event notifiers to event notifier group + * + * @param notifiers collection of event notifiers to be added + * @return true if successful + */ + public boolean addNotifiers(Collection notifiers) + { + return _group.addAll(notifiers); + } + + /* ------------------------------------------------------------ */ + /** + * Remove event notifier from event notifier group + * + * @param notifier event notifier to be removed + * @return true if successful + */ + public boolean removeNotifier(EventNotifier notifier) + { + return _group.remove(notifier); + } + + /* ------------------------------------------------------------ */ + /** + * Remove a collection of event notifiers from event notifier group + * + * @param notifiers collection of event notifiers to be removed + * @return true if successful + */ + public boolean removeNotifiers(Collection notifiers) + { + return _group.removeAll(notifiers); + } + + /* ------------------------------------------------------------ */ + /** + * Invoke the notify() method of each of the notifiers in group + * + * @see org.eclipse.jetty.monitor.jmx.EventNotifier#notify(org.eclipse.jetty.monitor.jmx.EventTrigger, org.eclipse.jetty.monitor.jmx.EventState, long) + */ + public void notify(EventTrigger trigger, EventState state, long timestamp) + { + for (EventNotifier notifier: _group) + { + notifier.notify(trigger, state, timestamp); + } + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java new file mode 100644 index 00000000000..cfc824fdbd1 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.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.monitor.jmx; + +import java.io.IOException; +import java.lang.management.ManagementFactory; + +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * ServerConnection + * + * Provides ability to create a connection to either an external + * JMX server, or a loopback connection to the internal one. + */ +public class ServiceConnection +{ + private static final Logger LOG = Log.getLogger(ServiceConnection.class); + + private String _serviceUrl; + private MBeanServer _server; + private JMXConnectorServer _connectorServer; + private JMXConnector _serverConnector; + private MBeanServerConnection _serviceConnection; + + /* ------------------------------------------------------------ */ + /** + * Construct a loopback connection to an internal server + * + * @throws IOException + */ + public ServiceConnection() + throws IOException + { + this(null); + } + + /* ------------------------------------------------------------ */ + /** + * Construct a connection to specified server + * + * @param url URL of JMX server + * @throws IOException + */ + public ServiceConnection(String url) + throws IOException + { + _serviceUrl = url; + } + + /** + * Retrieve an external URL for the JMX server + * + * @return service URL + */ + public String getServiceUrl() + { + return _serviceUrl; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve a connection to MBean server + * + * @return connection to MBean server + */ + public MBeanServerConnection getConnection() + { + return _serviceConnection; + } + + public void connect() + throws IOException + { + if (_serviceConnection == null) + { + if (_serviceUrl == null) + openLoopbackConnection(); + else + openServerConnection(_serviceUrl); + } + } + /* ------------------------------------------------------------ */ + /** + * Open a loopback connection to local JMX server + * + * @throws IOException + */ + private void openLoopbackConnection() + throws IOException + { + _server = ManagementFactory.getPlatformMBeanServer(); + + JMXServiceURL serviceUrl = new JMXServiceURL("service:jmx:rmi://"); + _connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl, null, _server); + _connectorServer.start(); + + _serviceUrl = _connectorServer.getAddress().toString(); + + _serverConnector = JMXConnectorFactory.connect(_connectorServer.getAddress()); + _serviceConnection = _serverConnector.getMBeanServerConnection(); + } + + /* ------------------------------------------------------------ */ + /** + * Open a connection to remote JMX server + * + * @param url + * @throws IOException + */ + private void openServerConnection(String url) + throws IOException + { + _serviceUrl = url; + + JMXServiceURL serviceUrl = new JMXServiceURL(_serviceUrl); + _serverConnector = JMXConnectorFactory.connect(serviceUrl); + _serviceConnection = _serverConnector.getMBeanServerConnection(); + } + + /* ------------------------------------------------------------ */ + /** + * Close the connections + */ + public void disconnect() + { + try + { + if (_serverConnector != null) + { + _serverConnector.close(); + _serviceConnection = null; + } + if (_connectorServer != null) + { + _connectorServer.stop(); + _connectorServer = null; + } + } + catch (Exception ex) + { + LOG.debug(ex); + } + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/SimpleAction.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/SimpleAction.java new file mode 100644 index 00000000000..650d81f3ee2 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/SimpleAction.java @@ -0,0 +1,46 @@ +// +// ======================================================================== +// 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.jmx; + +import java.security.InvalidParameterException; + + + +/* ------------------------------------------------------------ */ +/** + */ +public class SimpleAction extends MonitorAction +{ + public SimpleAction(EventTrigger trigger, EventNotifier notifier, long pollInterval) + throws InvalidParameterException + { + super(trigger,notifier,pollInterval); + } + + /* ------------------------------------------------------------ */ + /** + * @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) + { + System.out.printf("Action time: %tc%n", timestamp); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java new file mode 100644 index 00000000000..aa39b09889a --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java @@ -0,0 +1,34 @@ +// +// ======================================================================== +// 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.thread; + + +/* ------------------------------------------------------------ */ +/** + */ +public class ThreadMonitorException extends Exception +{ + private static final long serialVersionUID = -4345223166315716918L; + + public ThreadMonitorException(String message, StackTraceElement[] stackTrace) + { + super(message); + setStackTrace(stackTrace); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java new file mode 100644 index 00000000000..8e85c38667a --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java @@ -0,0 +1,202 @@ +// +// ======================================================================== +// 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.thread; + + +/* ------------------------------------------------------------ */ +/** + */ +public class ThreadMonitorInfo +{ + private Thread _thread; + private StackTraceElement[] _stackTrace; + + private boolean _threadSpinning = false; + private int _traceCount = -1; + + private long _prevCpuTime; + private long _prevSampleTime; + private long _currCpuTime; + private long _currSampleTime; + + + /* ------------------------------------------------------------ */ + /** + * Instantiates a new thread monitor info. + * + * @param thread the thread this object is created for + */ + public ThreadMonitorInfo(Thread thread) + { + _thread = thread; + } + + /* ------------------------------------------------------------ */ + /** + * @return Id of the thread + */ + public long getThreadId() + { + return _thread.getId(); + } + + /* ------------------------------------------------------------ */ + /** + * Gets the thread name. + * + * @return the thread name + */ + public String getThreadName() + { + return _thread.getName(); + } + + /* ------------------------------------------------------------ */ + /** + * Gets the thread state. + * + * @return the thread state + */ + public String getThreadState() + { + return _thread.getState().toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Gets the stack trace. + * + * @return the stack trace + */ + public StackTraceElement[] getStackTrace() + { + return _stackTrace; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the stack trace. + * + * @param stackTrace the new stack trace + */ + public void setStackTrace(StackTraceElement[] stackTrace) + { + _stackTrace = stackTrace; + } + + /* ------------------------------------------------------------ */ + /** + * Checks if is spinning. + * + * @return true, if is spinning + */ + public boolean isSpinning() + { + return _threadSpinning; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the spinning flag. + * + * @param value the new value + */ + public void setSpinning(boolean value) + { + _threadSpinning = value; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the trace count. + * + * @param traceCount the new trace count + */ + public void setTraceCount(int traceCount) + { + _traceCount = traceCount; + } + + /* ------------------------------------------------------------ */ + /** + * Gets the trace count. + * + * @return the trace count + */ + public int getTraceCount() + { + return _traceCount; + } + + /* ------------------------------------------------------------ */ + /** + * @return the CPU time of the thread + */ + public long getCpuTime() + { + return _currCpuTime; + } + + /* ------------------------------------------------------------ */ + /** + * Set the CPU time. + * + * @param ns new CPU time + */ + public void setCpuTime(long ns) + { + _prevCpuTime = _currCpuTime; + _currCpuTime = ns; + } + + /* ------------------------------------------------------------ */ + /** + * @return the time of sample + */ + public long getSampleTime() + { + return _currSampleTime; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the sample time. + * + * @param ns the time of sample + */ + public void setSampleTime(long ns) + { + _prevSampleTime = _currSampleTime; + _currSampleTime = ns; + } + + /* ------------------------------------------------------------ */ + /** + * Gets the CPU utilization. + * + * @return the CPU utilization percentage + */ + public float getCpuUtilization() + { + long elapsedCpuTime = _currCpuTime - _prevCpuTime; + long elapsedNanoTime = _currSampleTime - _prevSampleTime; + + return elapsedNanoTime > 0 ? Math.min((elapsedCpuTime * 100.0f) / elapsedNanoTime, 100.0f) : 0; + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java new file mode 100644 index 00000000000..78a1e869351 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java @@ -0,0 +1,168 @@ +// +// ======================================================================== +// 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.triggers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.monitor.jmx.EventState; +import org.eclipse.jetty.monitor.jmx.EventTrigger; + + +/* ------------------------------------------------------------ */ +/** + * AggregateEventTrigger + * + * EventTrigger aggregation that executes every aggregated event + * triggers in left to right order, and returns match if any one + * of them have returned match. + */ +public class AggregateEventTrigger extends EventTrigger +{ + protected final List _triggers; + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger + */ + public AggregateEventTrigger() + { + _triggers = new ArrayList(); + } + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the list + * of event triggers to be aggregated by this trigger + * + * @param triggers list of event triggers to add + */ + public AggregateEventTrigger(List triggers) + { + _triggers = new ArrayList(triggers); + } + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the array + * of event triggers to be aggregated by this trigger + * + * @param triggers list of event triggers to add + */ + public AggregateEventTrigger(EventTrigger... triggers) + { + _triggers = Arrays.asList(triggers); + } + + /* ------------------------------------------------------------ */ + /** + * @param trigger + */ + public void add(EventTrigger trigger) + { + _triggers.add(trigger); + } + + /* ------------------------------------------------------------ */ + /** + * @param triggers + */ + public void addAll(List triggers) + { + _triggers.addAll(triggers); + } + + /* ------------------------------------------------------------ */ + /** + * @param triggers + */ + public void addAll(EventTrigger... triggers) + { + _triggers.addAll(Arrays.asList(triggers)); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event state associated with specified invocation + * of the event trigger match method. This event trigger retrieves + * the combined event state of all aggregated event triggers. + * + * @param timestamp time stamp associated with invocation + * @return event state or null if not found + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#getState(long) + */ + @Override + public EventState getState(long timestamp) + { + EventState state = new EventState(); + + for (EventTrigger trigger : _triggers) + { + EventState subState = trigger.getState(timestamp); + if (subState != null) + { + state.addAll(subState.values()); + } + } + + return state; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#match(long) + */ + @Override + public boolean match(long timestamp) throws Exception + { + boolean result = false; + for(EventTrigger trigger : _triggers) + { + result = trigger.match(timestamp) ? true : result; + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "AND(triger1,trigger2,...)". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + int cnt = 0; + StringBuilder result = new StringBuilder(); + + result.append("ANY("); + for (EventTrigger trigger : _triggers) + { + result.append(cnt++ > 0 ? "," : ""); + result.append(trigger); + } + result.append(')'); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java new file mode 100644 index 00000000000..5a31337f7c1 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java @@ -0,0 +1,134 @@ +// +// ======================================================================== +// 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.triggers; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.monitor.jmx.EventState; +import org.eclipse.jetty.monitor.jmx.EventTrigger; + + + +/* ------------------------------------------------------------ */ +/** + * AndEventTrigger + * + * EventTrigger aggregation using logical AND operation + * that executes matching of the aggregated event triggers + * in left to right order + */ +public class AndEventTrigger extends EventTrigger +{ + protected final List _triggers; + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the list + * of event triggers to be aggregated by this trigger + * + * @param triggers list of event triggers to add + */ + public AndEventTrigger(List triggers) + { + _triggers = triggers; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the array + * of event triggers to be aggregated by this trigger + * + * @param triggers array of event triggers to add + */ + public AndEventTrigger(EventTrigger... triggers) + { + _triggers = Arrays.asList(triggers); + } + + /* ------------------------------------------------------------ */ + /** + * Verify if the event trigger conditions are in the + * appropriate state for an event to be triggered. + * This event trigger will match if all aggregated + * event triggers would return a match. + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#match(long) + */ + public boolean match(long timestamp) + throws Exception + { + for(EventTrigger trigger : _triggers) + { + if (!trigger.match(timestamp)) + return false; + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event state associated with specified invocation + * of the event trigger match method. This event trigger retrieves + * the combined event state of all aggregated event triggers. + * + * @param timestamp time stamp associated with invocation + * @return event state or null if not found + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#getState(long) + */ + @Override + public EventState getState(long timestamp) + { + EventState state = new EventState(); + + for (EventTrigger trigger : _triggers) + { + EventState subState = trigger.getState(timestamp); + state.addAll(subState.values()); + } + + return state; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "AND(triger1,trigger2,...)". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + int cnt = 0; + StringBuilder result = new StringBuilder(); + + result.append("AND("); + for (EventTrigger trigger : _triggers) + { + result.append(cnt++ > 0 ? "," : ""); + result.append(trigger); + } + result.append(')'); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java new file mode 100644 index 00000000000..4f792c4331c --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java @@ -0,0 +1,239 @@ +// +// ======================================================================== +// 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.triggers; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; + +import org.eclipse.jetty.monitor.JMXMonitor; +import org.eclipse.jetty.monitor.jmx.EventState; +import org.eclipse.jetty.monitor.jmx.EventTrigger; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * AttrEventTrigger + * + * Event trigger that polls a value of an MXBean attribute + * and matches every invocation of this trigger. It can be + * used to send notifications of the value of an attribute + * of the MXBean being polled at a certain interval, or as + * a base class for the event triggers that match the + * value of an attribute of the MXBean being polled against + * some specified criteria. + */ +public class AttrEventTrigger> + extends EventTrigger +{ + private static final Logger LOG = Log.getLogger(AttrEventTrigger.class); + + private final ObjectName _nameObject; + + protected final String _objectName; + protected final String _attributeName; + protected Map> _states; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public AttrEventTrigger(String objectName, String attributeName) + throws MalformedObjectNameException, IllegalArgumentException + { + if (objectName == null) + throw new IllegalArgumentException("Object name cannot be null"); + if (attributeName == null) + throw new IllegalArgumentException("Attribute name cannot be null"); + + _states = new ConcurrentHashMap>(); + + _objectName = objectName; + _attributeName = attributeName; + + _nameObject = new ObjectName(_objectName); + } + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger. + * + * @param nameObject object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * + * @throws IllegalArgumentException + */ + public AttrEventTrigger(ObjectName nameObject, String attributeName) + throws IllegalArgumentException + { + if (nameObject == null) + throw new IllegalArgumentException("Object name cannot be null"); + if (attributeName == null) + throw new IllegalArgumentException("Attribute name cannot be null"); + + _states = new ConcurrentHashMap>(); + + _objectName = nameObject.toString(); + _attributeName = attributeName; + + _nameObject = nameObject; + } + + /* ------------------------------------------------------------ */ + /** + * Verify if the event trigger conditions are in the + * appropriate state for an event to be triggered. + * This event trigger uses the match(Comparable) + * method to compare the value of the MXBean attribute + * to the conditions specified by the subclasses. + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#match(long) + */ + @SuppressWarnings("unchecked") + public final boolean match(long timestamp) + throws Exception + { + MBeanServerConnection serverConnection = JMXMonitor.getServiceConnection(); + + TYPE value = null; + try + { + int pos = _attributeName.indexOf('.'); + if (pos < 0) + value = (TYPE)serverConnection.getAttribute(_nameObject,_attributeName); + else + value = getValue((CompositeData)serverConnection.getAttribute(_nameObject, _attributeName.substring(0, pos)), + _attributeName.substring(pos+1)); + } + catch (Exception ex) + { + LOG.debug(ex); + } + + boolean result = false; + if (value != null) + { + result = match(value); + + if (result || getSaveAll()) + { + _states.put(timestamp, + new EventState(this.getID(), this.getNameString(), value)); + } + } + + return result; + } + + + /* ------------------------------------------------------------ */ + /** + * Verify if the event trigger conditions are in the + * appropriate state for an event to be triggered. + * Allows subclasses to override the default behavior + * that matches every invocation of this trigger + */ + public boolean match(Comparable value) + { + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event state associated with specified invocation + * of the event trigger match method. + * + * @param timestamp time stamp associated with invocation + * @return event state or null if not found + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#getState(long) + */ + @Override + public final EventState getState(long timestamp) + { + return _states.get(timestamp); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "[object_name:attribute_name]". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + return getNameString(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "[object_name:attribute_name]". Allows + * subclasses to override the name string used to identify + * this event trigger in the event state object as well as + * string representation of the subclasses. + * + * @return string representation of the event trigger + */ + protected String getNameString() + { + StringBuilder result = new StringBuilder(); + + result.append('['); + result.append(_objectName); + result.append(":"); + result.append(_attributeName); + result.append("]"); + + return result.toString(); + } + + protected boolean getSaveAll() + { + return true; + } + + protected TYPE getValue(CompositeData compValue, String fieldName) + { + int pos = fieldName.indexOf('.'); + if (pos < 0) + return (TYPE)compValue.get(fieldName); + else + return getValue((CompositeData)compValue.get(fieldName.substring(0, pos)), + fieldName.substring(pos+1)); + + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java new file mode 100644 index 00000000000..c945b48412b --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java @@ -0,0 +1,89 @@ +// +// ======================================================================== +// 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.triggers; + +import javax.management.MalformedObjectNameException; + + +/* ------------------------------------------------------------ */ +/** + * EqualToAttrEventTrigger + * + * Event trigger that polls a value of an MXBean attribute and + * checks if it is equal to specified value. + */ +public class EqualToAttrEventTrigger> extends AttrEventTrigger +{ + protected final TYPE _value; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as the + * target value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param value target value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public EqualToAttrEventTrigger(String objectName, String attributeName, TYPE value) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (value == null) + throw new IllegalArgumentException("Value cannot be null"); + + _value = value; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is equal to the specified value. + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_value) == 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "name=value". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder result = new StringBuilder(); + + result.append(getNameString()); + result.append("=="); + result.append(_value); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java new file mode 100644 index 00000000000..1eadf0703dd --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// 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.triggers; + +import javax.management.MalformedObjectNameException; + + +/* ------------------------------------------------------------ */ +/** + * GreaterThanAttrEventTrigger + * + * Event trigger that polls a value of an MXBean attribute and + * checks if it is greater than specified min value. + */ +public class GreaterThanAttrEventTrigger> extends AttrEventTrigger +{ + protected final TYPE _min; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as min + * value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param min minimum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public GreaterThanAttrEventTrigger(String objectName, String attributeName, TYPE min) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (min == null) + throw new IllegalArgumentException("Value cannot be null"); + + _min = min; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is greater than the min value. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_min) > 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "min> extends AttrEventTrigger +{ + protected final TYPE _min; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as min + * value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param min minimum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public GreaterThanOrEqualToAttrEventTrigger(String objectName, String attributeName, TYPE min) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (min == null) + throw new IllegalArgumentException("Value cannot be null"); + + _min = min; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is greater than or equal to the min value. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_min) >= 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "min<=name". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder result = new StringBuilder(); + + result.append(_min); + result.append("<="); + result.append(getNameString()); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java new file mode 100644 index 00000000000..4d0931c4584 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// 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.triggers; + +import javax.management.MalformedObjectNameException; + + +/* ------------------------------------------------------------ */ +/** + * LessThanAttrEventTrigger + * + * Event trigger that polls a value of an MXBean attribute and + * checks if it is greater than specified max value. + */ +public class LessThanAttrEventTrigger> extends AttrEventTrigger +{ + protected final TYPE _max; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as max + * value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param max maximum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public LessThanAttrEventTrigger(String objectName, String attributeName, TYPE max) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (max == null) + throw new IllegalArgumentException("Value cannot be null"); + + _max = max; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is less than the min value. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_max) < 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "name> extends AttrEventTrigger +{ + protected final TYPE _max; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as max + * value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param max maximum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public LessThanOrEqualToAttrEventTrigger(String objectName, String attributeName, TYPE max) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (max == null) + throw new IllegalArgumentException("Value cannot be null"); + + _max = max; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is less than or equal to the max value. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_max) <= 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "name<=max". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder result = new StringBuilder(); + + result.append(getNameString()); + result.append("<="); + result.append(_max); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/OrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/OrEventTrigger.java new file mode 100644 index 00000000000..176c5e3547b --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/OrEventTrigger.java @@ -0,0 +1,138 @@ +// +// ======================================================================== +// 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.triggers; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.monitor.jmx.EventState; +import org.eclipse.jetty.monitor.jmx.EventTrigger; + + +/* ------------------------------------------------------------ */ +/** + * AndEventTrigger + * + * EventTrigger aggregation using logical OR operation + * that executes matching of the aggregated event triggers + * in left to right order + */ +public class OrEventTrigger + extends EventTrigger +{ + private final List _triggers; + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the list + * of event triggers to be aggregated by this trigger + * + * @param triggers list of event triggers to add + */ + public OrEventTrigger(List triggers) + { + _triggers = triggers; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an event trigger and associate the array + * of event triggers to be aggregated by this trigger + * + * @param triggers array of event triggers to add + */ + public OrEventTrigger(EventTrigger... triggers) + { + _triggers = Arrays.asList(triggers); + } + + /* ------------------------------------------------------------ */ + /** + * Verify if the event trigger conditions are in the + * appropriate state for an event to be triggered. + * This event trigger will match if any of aggregated + * event triggers would return a match. + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#match(long) + */ + public boolean match(long timestamp) + throws Exception + { + for(EventTrigger trigger : _triggers) + { + if (trigger.match(timestamp)) + return true; + } + return false; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the event state associated with specified invocation + * of the event trigger match method. This event trigger retrieves + * the combined event state of all aggregated event triggers. + * + * @param timestamp time stamp associated with invocation + * @return event state or null if not found + * + * @see org.eclipse.jetty.monitor.jmx.EventTrigger#getState(long) + */ + @Override + @SuppressWarnings("unchecked") + public EventState getState(long timestamp) + { + EventState state = new EventState(); + + for (EventTrigger trigger : _triggers) + { + EventState subState = trigger.getState(timestamp); + if (subState!=null) + { + state.addAll(subState.values()); + } + } + + return state; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "OR(triger1,trigger2,...)". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + int cnt = 0; + StringBuilder result = new StringBuilder(); + + result.append("OR("); + for (EventTrigger trigger : _triggers) + { + result.append(cnt++ > 0 ? "," : ""); + result.append(trigger); + } + result.append(')'); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java new file mode 100644 index 00000000000..7810c0fe3b8 --- /dev/null +++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java @@ -0,0 +1,100 @@ +// +// ======================================================================== +// 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.triggers; + +import javax.management.MalformedObjectNameException; + + +/* ------------------------------------------------------------ */ +/** + * RangeAttrEventTrigger + * + * Event trigger that polls a value of an MXBean attribute and + * checks if it is in a range from specified min value to + * specified max value. + */ +public class RangeAttrEventTrigger> extends AttrEventTrigger +{ + protected final TYPE _min; + protected final TYPE _max; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as min + * and max value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param min minimum value of the attribute + * @param max maximum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public RangeAttrEventTrigger(String objectName, String attributeName,TYPE min, TYPE max) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (min == null) + throw new IllegalArgumentException("Value cannot be null"); + if (max == null) + throw new IllegalArgumentException("Value cannot be null"); + + _min = min; + _max = max; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is in a range from specified min value to + * specified max value. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_min) > 0) &&(value.compareTo(_max) < 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "min> extends AttrEventTrigger +{ + protected final TYPE _min; + protected final TYPE _max; + + /* ------------------------------------------------------------ */ + /** + * Construct event trigger and specify the MXBean attribute + * that will be polled by this event trigger as well as min + * and max value of the attribute. + * + * @param objectName object name of an MBean to be polled + * @param attributeName name of an MBean attribute to be polled + * @param min minimum value of the attribute + * @param max maximum value of the attribute + * + * @throws MalformedObjectNameException + * @throws IllegalArgumentException + */ + public RangeInclAttrEventTrigger(String objectName, String attributeName,TYPE min, TYPE max) + throws MalformedObjectNameException, IllegalArgumentException + { + super(objectName,attributeName); + + if (min == null) + throw new IllegalArgumentException("Value cannot be null"); + if (max == null) + throw new IllegalArgumentException("Value cannot be null"); + + _min = min; + _max = max; + } + + /* ------------------------------------------------------------ */ + /** + * Compare the value of the MXBean attribute being polling + * to check if it is in a range from specified min value to + * specified max value including the range bounds. + * + * @see org.eclipse.jetty.monitor.triggers.AttrEventTrigger#match(java.lang.Comparable) + */ + @Override + public boolean match(Comparable value) + { + return (value.compareTo(_min) >= 0) &&(value.compareTo(_max) <= 0); + } + + /* ------------------------------------------------------------ */ + /** + * Returns the string representation of this event trigger + * in the format "min<=name<=max". + * + * @return string representation of the event trigger + * + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder result = new StringBuilder(); + + result.append(_min); + result.append("<="); + result.append(getNameString()); + result.append("<="); + result.append(_max); + + return result.toString(); + } +} diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java new file mode 100644 index 00000000000..72d60606033 --- /dev/null +++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java @@ -0,0 +1,525 @@ +// +// ======================================================================== +// 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 static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.management.ManagementFactory; +import java.util.Collection; +import java.util.Iterator; +import java.util.TreeSet; + +import javax.management.MBeanServer; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.jmx.MBeanContainer; +import org.eclipse.jetty.monitor.jmx.ConsoleNotifier; +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.AndEventTrigger; +import org.eclipse.jetty.monitor.triggers.AttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.EqualToAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.GreaterThanOrEqualToAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.LessThanAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.OrEventTrigger; +import org.eclipse.jetty.monitor.triggers.RangeAttrEventTrigger; +import org.eclipse.jetty.monitor.triggers.RangeInclAttrEventTrigger; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/* ------------------------------------------------------------ */ +/** + */ +public class AttrEventTriggerTest +{ + private static final Logger LOG = Log.getLogger(AttrEventTriggerTest.class); + + private Server _server; + private TestHandler _handler; + private RequestCounter _counter; + private JMXMonitor _monitor; + private HttpClient _client; + private String _requestUrl; + private MBeanContainer _mBeanContainer; + + @Before + public void setUp() + throws Exception + { + File docRoot = new File("target/test-output/docroot/"); + docRoot.mkdirs(); + docRoot.deleteOnExit(); + + System.setProperty("org.eclipse.jetty.util.log.DEBUG",""); + _server = new Server(); + + ServerConnector connector = new ServerConnector(_server); + connector.setPort(0); + _server.setConnectors(new Connector[] {connector}); + + _handler = new TestHandler(); + _server.setHandler(_handler); + + MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + _mBeanContainer = new MBeanContainer(mBeanServer); + _server.addBean(_mBeanContainer,true); + _server.addBean(Log.getLog()); + + _counter = _handler.getRequestCounter(); + _server.addBean(_counter); + + _server.start(); + + startClient(); + + _monitor = new JMXMonitor(); + + int port = connector.getLocalPort(); + _requestUrl = "http://localhost:"+port+ "/"; + } + + @After + public void tearDown() + throws Exception + { + stopClient(); + + _mBeanContainer.destroy(); + + if (_server != null) + { + _server.stop(); + _server = null; + } + } + + @Test + public void testNoCondition() + throws Exception + { + long requestCount = 10; + + AttrEventTrigger trigger = + new AttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter"); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(1,requestCount); + assertEquals(result, action.getHits()); + } + + @Test + public void testEqual_TRUE() + throws Exception + { + long requestCount = 10; + long testValue = 5; + + EqualToAttrEventTrigger trigger = + new EqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter",testValue); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(testValue); + assertEquals(result, action.getHits()); + } + + @Test + public void testEqual_FALSE() + throws Exception + { + long requestCount = 10; + long testValue = 11; + + EqualToAttrEventTrigger trigger = + new EqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testValue); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(); + assertEquals(result, action.getHits()); + } + + @Test + public void testLowerLimit() + throws Exception + { + long requestCount = 10; + long testRangeLow = 5; + + GreaterThanAttrEventTrigger trigger = + new GreaterThanAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(6,10); + assertEquals(result, action.getHits()); + } + + @Test + public void testLowerLimitIncl() + throws Exception + { + long requestCount = 10; + long testRangeLow = 5; + + GreaterThanOrEqualToAttrEventTrigger trigger = + new GreaterThanOrEqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(5,10); + assertEquals(result, action.getHits()); + } + + @Test + public void testUpperLimit() + throws Exception + { + long requestCount = 10; + long testRangeHigh = 5; + + LessThanAttrEventTrigger trigger = + new LessThanAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeHigh); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(1,4); + assertEquals(result, action.getHits()); + } + + + @Test + public void testUpperLimitIncl() + throws Exception + { + long requestCount = 10; + long testRangeHigh = 5; + + LessThanOrEqualToAttrEventTrigger trigger = + new LessThanOrEqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeHigh); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(1,5); + assertEquals(result, action.getHits()); + } + + @Test + public void testRangeInclusive() + throws Exception + { + long requestCount = 10; + long testRangeLow = 3; + long testRangeHigh = 8; + + RangeInclAttrEventTrigger trigger = + new RangeInclAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow, testRangeHigh); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(testRangeLow,testRangeHigh); + assertEquals(result, action.getHits()); + } + + @Test + public void testInsideRangeExclusive() + throws Exception + { + long requestCount = 10; + long testRangeLow = 3; + long testRangeHigh = 8; + + RangeAttrEventTrigger trigger = + new RangeAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow, testRangeHigh); + + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(testRangeLow+1,testRangeHigh-1); + assertEquals(result, action.getHits()); + } + + @Test + public void testRangeComposite() + throws Exception + { + long requestCount = 10; + long testRangeLow = 4; + long testRangeHigh = 7; + + GreaterThanAttrEventTrigger trigger1 = + new GreaterThanAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow); + LessThanOrEqualToAttrEventTrigger trigger2 = + new LessThanOrEqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeHigh); + AndEventTrigger trigger = new AndEventTrigger(trigger1, trigger2); + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(testRangeLow+1,testRangeHigh); + assertEquals(result, action.getHits()); + } + + @Test + public void testRangeOuter() + throws Exception + { + long requestCount = 10; + long testRangeLow = 4; + long testRangeHigh = 7; + + LessThanOrEqualToAttrEventTrigger trigger1 = + new LessThanOrEqualToAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeLow); + GreaterThanAttrEventTrigger trigger2 = + new GreaterThanAttrEventTrigger("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter", + testRangeHigh); + OrEventTrigger trigger = new OrEventTrigger(trigger1, trigger2); + EventNotifier notifier = new ConsoleNotifier("%s"); + CounterAction action = new CounterAction(trigger, notifier, 500, 100); + + performTest(action, requestCount, 1000); + + ResultSet result = new ResultSet(1,testRangeLow,testRangeHigh+1, requestCount); + assertEquals(result, action.getHits()); + } + + protected void performTest(MonitorAction action, long count, long interval) + throws Exception + { + _monitor.addActions(action); + + for (long cnt=0; cnt < count; cnt++) + { + try + { + //LOG.debug("Request: %s", _requestUrl); + ContentResponse r3sponse = _client.GET(_requestUrl); + + //ContentExchange getExchange = new ContentExchange(); + //getExchange.setURL(_requestUrl); + //getExchange.setMethod(HttpMethods.GET); + + //_client.send(getExchange); + //int state = getExchange.waitForDone(); + + String content = ""; + //int responseStatus = getExchange.getResponseStatus(); + if (r3sponse.getStatus() == HttpStatus.OK_200) + { + content = r3sponse.getContentAsString(); + } + else + { + LOG.info("response status", r3sponse.getStatus()); + } + + assertEquals(HttpStatus.OK_200,r3sponse.getStatus()); + Thread.sleep(interval); + } + catch (InterruptedException ex) + { + break; + } + } + + Thread.sleep(interval); + + _monitor.removeActions(action); + } + + protected void startClient()//Realm realm) + throws Exception + { + _client = new HttpClient(); + //_client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + //if (realm != null){ +// _client.setRealmResolver(new SimpleRealmResolver(realm)); + //} + _client.start(); + } + + protected void stopClient() + throws Exception + { + if (_client != null) + { + _client.stop(); + _client = null; + } + } + + protected static class TestHandler + extends AbstractHandler + { + private RequestCounter _counter = new RequestCounter(); + + public void handle(String target, Request baseRequest, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + if (baseRequest.isHandled()) { + return; + } + _counter.increment(); + + response.setContentType("text/plain"); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter writer = response.getWriter(); + writer.println("===TEST RESPONSE==="); + baseRequest.setHandled(true); + } + + public RequestCounter getRequestCounter() + { + return _counter; + } + } + + protected static class ResultSet extends TreeSet + { + public ResultSet() {} + + public ResultSet(long value) + { + add(value); + } + + public ResultSet(long start, long end) + { + addEntries(start, end); + } + + public ResultSet(long start, long pause, long resume, long end) + { + addEntries(start, pause); + addEntries(resume, end); + } + + public void addEntries(long start, long stop) + { + if (start > 0 && stop > 0) + { + for(long idx=start; idx <= stop; idx++) + { + add(idx); + } + } + } + + public boolean equals(ResultSet set) + { + return (this.size() == set.size()) && containsAll(set); + } + } + + protected static class CounterAction + extends MonitorAction + { + private ResultSet _hits = new ResultSet(); + + public CounterAction(EventTrigger trigger, EventNotifier notifier, long interval, long delay) + { + super(trigger, notifier, interval, delay); + } + + public void execute(EventTrigger trigger, EventState state, long timestamp) + { + if (trigger != null && state != null) + { + Collection values = state.values(); + + Iterator it = values.iterator(); + while(it.hasNext()) + { + TriggerState entry = (TriggerState)it.next(); + Object value = entry.getValue(); + if (value != null) + { + _hits.add((Long)value); + } + } + } + } + + public ResultSet getHits() + { + return _hits; + } + } +} diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java new file mode 100644 index 00000000000..0dcd6f43ecf --- /dev/null +++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java @@ -0,0 +1,48 @@ +// +// ======================================================================== +// 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 org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.annotation.ManagedOperation; + + +@ManagedObject("TEST: Request Counter") +public class RequestCounter +{ + public long _counter; + + @ManagedAttribute("Get the value of the counter") + public synchronized long getCounter() + { + return _counter; + } + + @ManagedOperation("Increment the value of the counter") + public synchronized void increment() + { + _counter++; + } + + @ManagedOperation("Reset the counter") + public synchronized void reset() + { + _counter = 0; + } +} diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java new file mode 100644 index 00000000000..1cff36bf17b --- /dev/null +++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java @@ -0,0 +1,163 @@ +// +// ======================================================================== +// 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 static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; +import org.junit.Test; + + +/* ------------------------------------------------------------ */ +/** + */ +public class ThreadMonitorTest +{ + public final static int DURATION=4000; + + @Test + public void monitorTest() throws Exception + { + ((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setHideStacks(true); + ((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setSource(false); + + final AtomicInteger countLogs=new AtomicInteger(0); + final AtomicInteger countSpin=new AtomicInteger(0); + + ThreadMonitor monitor = new ThreadMonitor(1000,50,1,1) + { + @Override + protected void logThreadInfo(boolean logAll) + { + if (logAll) + countLogs.incrementAndGet(); + else + countSpin.incrementAndGet(); + super.logThreadInfo(logAll); + } + }; + monitor.setDumpable(new Dumpable() + { + public void dump(Appendable out, String indent) throws IOException + { + out.append(dump()); + } + + public String dump() + { + return "Dump Spinning"; + } + }); + + monitor.logCpuUsage(2000,0); + monitor.start(); + + Random rnd = new Random(); + for (long cnt=0; cnt<100; cnt++) + { + long value = rnd.nextLong() % 50 + 50; + Sleeper sleeper = new Sleeper(value); + Thread runner = new Thread(sleeper); + runner.setDaemon(true); + runner.start(); + } + + Spinner spinner = new Spinner(); + Thread runner = new Thread(spinner); + runner.start(); + + Thread.sleep(DURATION); + + spinner.setDone(); + monitor.stop(); + + assertTrue(countLogs.get() >= 1); + assertTrue(countSpin.get() >= 2); + } + + + private class Spinner implements Runnable + { + private volatile boolean done = false; + + /* ------------------------------------------------------------ */ + public void setDone() + { + done = true; + } + + /* ------------------------------------------------------------ */ + public void run() + { + spin(); + } + + /* ------------------------------------------------------------ */ + public void spin() + { + long result=-1; + long end=System.currentTimeMillis()+DURATION+1000; + while (!done && System.currentTimeMillis() 1 ? fn(value-1) : 1; + + Thread.sleep(50); + + return result; + } + } +} diff --git a/jetty-monitor/src/test/resources/jetty-logging.properties b/jetty-monitor/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..e94eed36c1a --- /dev/null +++ b/jetty-monitor/src/test/resources/jetty-logging.properties @@ -0,0 +1,3 @@ +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +#org.eclipse.jetty.LEVEL=DEBUG +org.eclipse.jetty.monitor.LEVEL=DEBUG diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java index 80deac4fb59..988ebb244f2 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java @@ -115,7 +115,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration for (Resource r:matchingResources) { - context.getMetaData().addContainerJar(r); + context.getMetaData().addContainerResource(r); } } diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java index caa37762497..d4298be1d9a 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java @@ -25,7 +25,9 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.Trie; /** * MSIE (Microsoft Internet Explorer) SSL Rule. @@ -38,7 +40,7 @@ public class MsieSslRule extends Rule { private static final int IEv5 = '5'; private static final int IEv6 = '6'; - private static StringMap __IE6_BadOS = new StringMap(); + private static Trie __IE6_BadOS = new ArrayTernaryTrie<>(); { __IE6_BadOS.put("NT 5.01", Boolean.TRUE); __IE6_BadOS.put("NT 5.0",Boolean.TRUE); diff --git a/jetty-server/src/main/config/etc/jetty-https.xml b/jetty-server/src/main/config/etc/jetty-https.xml index 93c8fe1954d..a6bef16ff1e 100644 --- a/jetty-server/src/main/config/etc/jetty-https.xml +++ b/jetty-server/src/main/config/etc/jetty-https.xml @@ -2,64 +2,12 @@ - - + - - + - - - - - - - - - - - - - - - - - - /etc/keystore - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - OBF:1u2u1wml1z7s1z7a1wnl1u2g - /etc/keystore - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - - - - SSL_RSA_WITH_DES_CBC_SHA - SSL_DHE_RSA_WITH_DES_CBC_SHA - SSL_DHE_DSS_WITH_DES_CBC_SHA - SSL_RSA_EXPORT_WITH_RC4_40_MD5 - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA - - - - - - - - - - - - - - - - - - @@ -71,7 +19,7 @@ - + @@ -91,7 +39,7 @@ - + 30000 diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml index 984eb6a8879..6e6eb054f0c 100644 --- a/jetty-server/src/main/config/etc/jetty-requestlog.xml +++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml @@ -14,12 +14,12 @@ - + /yyyy_mm_dd.request.log yyyy_MM_dd 90 true - false + true false GMT diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml new file mode 100644 index 00000000000..b4c3551aadc --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-ssl.xml @@ -0,0 +1,41 @@ + + + + + + + + + + / + + + / + + + + + SSL_RSA_WITH_DES_CBC_SHA + SSL_DHE_RSA_WITH_DES_CBC_SHA + SSL_DHE_DSS_WITH_DES_CBC_SHA + SSL_RSA_EXPORT_WITH_RC4_40_MD5 + SSL_RSA_EXPORT_WITH_DES40_CBC_SHA + SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + + + + + + + + + + + + + + + + + diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index dd78c540040..2d9b8596e47 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -44,8 +44,14 @@ - 10 - 200 + 10 + 200 + 60000 + false @@ -76,7 +82,7 @@ https - + 32768 8192 8192 diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java new file mode 100644 index 00000000000..5223e970049 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java @@ -0,0 +1,501 @@ +// +// ======================================================================== +// 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.server; + +import java.io.IOException; +import java.util.Locale; + +import javax.servlet.http.Cookie; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard + * NCSA common log format. Configuration options allow a choice between the + * standard Common Log Format (as used in the 3 log format) and the Combined Log + * Format (single log format). This log format can be output by most web + * servers, and almost all web log analysis software can understand these + * formats. + */ +public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog +{ + protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class); + + private static ThreadLocal _buffers = new ThreadLocal() + { + @Override + protected StringBuilder initialValue() + { + return new StringBuilder(256); + } + }; + + + private String[] _ignorePaths; + private boolean _extended; + private transient PathMap _ignorePathMap; + private boolean _logLatency = false; + private boolean _logCookies = false; + private boolean _logServer = false; + private boolean _logDispatch = false; + private boolean _preferProxiedForAddress; + private transient DateCache _logDateCache; + private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z"; + private Locale _logLocale = Locale.getDefault(); + private String _logTimeZone = "GMT"; + + /* ------------------------------------------------------------ */ + /** + * Is logging enabled + */ + protected abstract boolean isEnabled(); + + /* ------------------------------------------------------------ */ + /** + * Write requestEntry out. (to disk or slf4j log) + */ + public abstract void write(String requestEntry) throws IOException; + + /* ------------------------------------------------------------ */ + /** + * Writes the request and response information to the output stream. + * + * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response) + */ + @Override + public void log(Request request, Response response) + { + try + { + if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) + return; + + if (!isEnabled()) + return; + + StringBuilder buf= _buffers.get(); + buf.setLength(0); + + if (_logServer) + { + buf.append(request.getServerName()); + buf.append(' '); + } + + String addr = null; + if (_preferProxiedForAddress) + { + addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString()); + } + + if (addr == null) + addr = request.getRemoteAddr(); + + buf.append(addr); + buf.append(" - "); + Authentication authentication=request.getAuthentication(); + if (authentication instanceof Authentication.User) + buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName()); + else + buf.append(" - "); + + buf.append(" ["); + if (_logDateCache != null) + buf.append(_logDateCache.format(request.getTimeStamp())); + else + buf.append(request.getTimeStamp()); + + buf.append("] \""); + buf.append(request.getMethod()); + buf.append(' '); + buf.append(request.getUri().toString()); + buf.append(' '); + buf.append(request.getProtocol()); + buf.append("\" "); + if (request.getHttpChannelState().isInitial()) + { + int status = response.getStatus(); + if (status <= 0) + status = 404; + buf.append((char)('0' + ((status / 100) % 10))); + buf.append((char)('0' + ((status / 10) % 10))); + buf.append((char)('0' + (status % 10))); + } + else + buf.append("Async"); + + long responseLength = response.getLongContentLength(); + if (responseLength >= 0) + { + buf.append(' '); + if (responseLength > 99999) + buf.append(responseLength); + else + { + if (responseLength > 9999) + buf.append((char)('0' + ((responseLength / 10000) % 10))); + if (responseLength > 999) + buf.append((char)('0' + ((responseLength / 1000) % 10))); + if (responseLength > 99) + buf.append((char)('0' + ((responseLength / 100) % 10))); + if (responseLength > 9) + buf.append((char)('0' + ((responseLength / 10) % 10))); + buf.append((char)('0' + (responseLength) % 10)); + } + buf.append(' '); + } + else + buf.append(" - "); + + + if (_extended) + logExtended(request, response, buf); + + if (_logCookies) + { + Cookie[] cookies = request.getCookies(); + if (cookies == null || cookies.length == 0) + buf.append(" -"); + else + { + buf.append(" \""); + for (int i = 0; i < cookies.length; i++) + { + if (i != 0) + buf.append(';'); + buf.append(cookies[i].getName()); + buf.append('='); + buf.append(cookies[i].getValue()); + } + buf.append('\"'); + } + } + + if (_logDispatch || _logLatency) + { + long now = System.currentTimeMillis(); + + if (_logDispatch) + { + long d = request.getDispatchTime(); + buf.append(' '); + buf.append(now - (d==0 ? request.getTimeStamp():d)); + } + + if (_logLatency) + { + buf.append(' '); + buf.append(now - request.getTimeStamp()); + } + } + + String log = buf.toString(); + write(log); + } + catch (IOException e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------ */ + /** + * Writes extended request and response information to the output stream. + * + * @param request request object + * @param response response object + * @param b StringBuilder to write to + * @throws IOException + */ + protected void logExtended(Request request, + Response response, + StringBuilder b) throws IOException + { + String referer = request.getHeader(HttpHeader.REFERER.toString()); + if (referer == null) + b.append("\"-\" "); + else + { + b.append('"'); + b.append(referer); + b.append("\" "); + } + + String agent = request.getHeader(HttpHeader.USER_AGENT.toString()); + if (agent == null) + b.append("\"-\" "); + else + { + b.append('"'); + b.append(agent); + b.append('"'); + } + } + + + + /** + * Set request paths that will not be logged. + * + * @param ignorePaths array of request paths + */ + public void setIgnorePaths(String[] ignorePaths) + { + _ignorePaths = ignorePaths; + } + + /** + * Retrieve the request paths that will not be logged. + * + * @return array of request paths + */ + public String[] getIgnorePaths() + { + return _ignorePaths; + } + + /** + * Controls logging of the request cookies. + * + * @param logCookies true - values of request cookies will be logged, + * false - values of request cookies will not be logged + */ + public void setLogCookies(boolean logCookies) + { + _logCookies = logCookies; + } + + /** + * Retrieve log cookies flag + * + * @return value of the flag + */ + public boolean getLogCookies() + { + return _logCookies; + } + + /** + * Controls logging of the request hostname. + * + * @param logServer true - request hostname will be logged, + * false - request hostname will not be logged + */ + public void setLogServer(boolean logServer) + { + _logServer = logServer; + } + + /** + * Retrieve log hostname flag. + * + * @return value of the flag + */ + public boolean getLogServer() + { + return _logServer; + } + + /** + * Controls logging of request processing time. + * + * @param logLatency true - request processing time will be logged + * false - request processing time will not be logged + */ + public void setLogLatency(boolean logLatency) + { + _logLatency = logLatency; + } + + /** + * Retrieve log request processing time flag. + * + * @return value of the flag + */ + public boolean getLogLatency() + { + return _logLatency; + } + + /** + * Controls logging of the request dispatch time + * + * @param value true - request dispatch time will be logged + * false - request dispatch time will not be logged + */ + public void setLogDispatch(boolean value) + { + _logDispatch = value; + } + + /** + * Retrieve request dispatch time logging flag + * + * @return value of the flag + */ + public boolean isLogDispatch() + { + return _logDispatch; + } + + /** + * Controls whether the actual IP address of the connection or + * the IP address from the X-Forwarded-For header will be logged. + * + * @param preferProxiedForAddress true - IP address from header will be logged, + * false - IP address from the connection will be logged + */ + public void setPreferProxiedForAddress(boolean preferProxiedForAddress) + { + _preferProxiedForAddress = preferProxiedForAddress; + } + + /** + * Retrieved log X-Forwarded-For IP address flag. + * + * @return value of the flag + */ + public boolean getPreferProxiedForAddress() + { + return _preferProxiedForAddress; + } + + /** + * Set the extended request log format flag. + * + * @param extended true - log the extended request information, + * false - do not log the extended request information + */ + public void setExtended(boolean extended) + { + _extended = extended; + } + + /** + * Retrieve the extended request log format flag. + * + * @return value of the flag + */ + @ManagedAttribute("use extended NCSA format") + public boolean isExtended() + { + return _extended; + } + + /** + * Set up request logging and open log file. + * + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected synchronized void doStart() throws Exception + { + if (_logDateFormat != null) + { + _logDateCache = new DateCache(_logDateFormat,_logLocale); + _logDateCache.setTimeZoneID(_logTimeZone); + } + + if (_ignorePaths != null && _ignorePaths.length > 0) + { + _ignorePathMap = new PathMap<>(); + for (int i = 0; i < _ignorePaths.length; i++) + _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]); + } + else + _ignorePathMap = null; + + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + _logDateCache = null; + super.doStop(); + } + + /** + * Set the timestamp format for request log entries in the file. + * If this is not set, the pre-formated request timestamp is used. + * + * @param format timestamp format string + */ + public void setLogDateFormat(String format) + { + _logDateFormat = format; + } + + /** + * Retrieve the timestamp format string for request log entries. + * + * @return timestamp format string. + */ + public String getLogDateFormat() + { + return _logDateFormat; + } + + /** + * Set the locale of the request log. + * + * @param logLocale locale object + */ + public void setLogLocale(Locale logLocale) + { + _logLocale = logLocale; + } + + /** + * Retrieve the locale of the request log. + * + * @return locale object + */ + public Locale getLogLocale() + { + return _logLocale; + } + + /** + * Set the timezone of the request log. + * + * @param tz timezone string + */ + public void setLogTimeZone(String tz) + { + _logTimeZone = tz; + } + + /** + * Retrieve the timezone of the request log. + * + * @return timezone string + */ + @ManagedAttribute("the timezone") + public String getLogTimeZone() + { + return _logTimeZone; + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java new file mode 100644 index 00000000000..047e3b6d401 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java @@ -0,0 +1,129 @@ +// +// ======================================================================== +// 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.server; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.ConcurrentArrayBlockingQueue; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * An asynchronously writing NCSA Request Log + */ +public class AsyncNCSARequestLog extends NCSARequestLog +{ + private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class); + private final BlockingQueue _queue; + private transient WriterThread _thread; + private boolean _warnedFull; + + public AsyncNCSARequestLog() + { + this(null,null); + } + + public AsyncNCSARequestLog(BlockingQueue queue) + { + this(null,queue); + } + + public AsyncNCSARequestLog(String filename) + { + this(filename,null); + } + + public AsyncNCSARequestLog(String filename,BlockingQueue queue) + { + super(filename); + if (queue==null) + queue=new ConcurrentArrayBlockingQueue.Bounded(1024); + _queue=queue; + } + + private class WriterThread extends Thread + { + WriterThread() + { + setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16)); + } + + @Override + public void run() + { + while (isRunning()) + { + try + { + String log = _queue.poll(10,TimeUnit.SECONDS); + if (log!=null) + AsyncNCSARequestLog.super.write(log); + + while(!_queue.isEmpty()) + { + log=_queue.poll(); + if (log!=null) + AsyncNCSARequestLog.super.write(log); + } + } + catch (IOException e) + { + LOG.warn(e); + } + catch (InterruptedException e) + { + LOG.ignore(e); + } + } + } + } + + @Override + protected synchronized void doStart() throws Exception + { + super.doStart(); + _thread = new WriterThread(); + _thread.start(); + } + + @Override + protected void doStop() throws Exception + { + _thread.interrupt(); + _thread.join(); + super.doStop(); + _thread=null; + } + + @Override + public void write(String log) throws IOException + { + if (!_queue.offer(log)) + { + if (_warnedFull) + LOG.warn("Log Queue overflow"); + _warnedFull=true; + } + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java index 1bfd730fb24..ef11b3e0c78 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java @@ -22,21 +22,12 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; -import java.util.Locale; import java.util.TimeZone; -import javax.servlet.http.Cookie; - -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.PathMap; -import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.RolloverFileOutputStream; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; /** * This {@link RequestLog} implementation outputs logs in the pseudo-standard @@ -45,37 +36,17 @@ import org.eclipse.jetty.util.log.Logger; * Format (single log format). This log format can be output by most web * servers, and almost all web log analysis software can understand these * formats. - * - */ - -/* ------------------------------------------------------------ */ -/** */ @ManagedObject("NCSA standard format request log") -public class NCSARequestLog extends AbstractLifeCycle implements RequestLog +public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog { - private static final Logger LOG = Log.getLogger(NCSARequestLog.class); - private String _filename; - private boolean _extended; private boolean _append; private int _retainDays; private boolean _closeOut; - private boolean _preferProxiedForAddress; - private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z"; private String _filenameDateFormat = null; - private Locale _logLocale = Locale.getDefault(); - private String _logTimeZone = "GMT"; - private String[] _ignorePaths; - private boolean _logLatency = false; - private boolean _logCookies = false; - private boolean _logServer = false; - private boolean _logDispatch = false; - private transient OutputStream _out; private transient OutputStream _fileOut; - private transient DateCache _logDateCache; - private transient PathMap _ignorePathMap; private transient Writer _writer; /* ------------------------------------------------------------ */ @@ -84,7 +55,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog */ public NCSARequestLog() { - _extended = true; + setExtended(true); _append = true; _retainDays = 31; } @@ -99,7 +70,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog */ public NCSARequestLog(String filename) { - _extended = true; + setExtended(true); _append = true; _retainDays = 31; setFilename(filename); @@ -136,7 +107,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog { return _filename; } - + /* ------------------------------------------------------------ */ /** * Retrieve the file name of the request log with the expanded @@ -153,71 +124,10 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog } /* ------------------------------------------------------------ */ - /** - * Set the timestamp format for request log entries in the file. - * If this is not set, the pre-formated request timestamp is used. - * - * @param format timestamp format string - */ - public void setLogDateFormat(String format) + @Override + protected boolean isEnabled() { - _logDateFormat = format; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve the timestamp format string for request log entries. - * - * @return timestamp format string. - */ - public String getLogDateFormat() - { - return _logDateFormat; - } - - /* ------------------------------------------------------------ */ - /** - * Set the locale of the request log. - * - * @param logLocale locale object - */ - public void setLogLocale(Locale logLocale) - { - _logLocale = logLocale; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve the locale of the request log. - * - * @return locale object - */ - public Locale getLogLocale() - { - return _logLocale; - } - - /* ------------------------------------------------------------ */ - /** - * Set the timezone of the request log. - * - * @param tz timezone string - */ - public void setLogTimeZone(String tz) - { - _logTimeZone = tz; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve the timezone of the request log. - * - * @return timezone string - */ - @ManagedAttribute("the timezone") - public String getLogTimeZone() - { - return _logTimeZone; + return (_fileOut != null); } /* ------------------------------------------------------------ */ @@ -243,30 +153,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog return _retainDays; } - /* ------------------------------------------------------------ */ - /** - * Set the extended request log format flag. - * - * @param extended true - log the extended request information, - * false - do not log the extended request information - */ - public void setExtended(boolean extended) - { - _extended = extended; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve the extended request log format flag. - * - * @return value of the flag - */ - @ManagedAttribute("use extended NCSA format") - public boolean isExtended() - { - return _extended; - } - /* ------------------------------------------------------------ */ /** * Set append to log flag. @@ -291,121 +177,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog return _append; } - /* ------------------------------------------------------------ */ - /** - * Set request paths that will not be logged. - * - * @param ignorePaths array of request paths - */ - public void setIgnorePaths(String[] ignorePaths) - { - _ignorePaths = ignorePaths; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve the request paths that will not be logged. - * - * @return array of request paths - */ - public String[] getIgnorePaths() - { - return _ignorePaths; - } - - /* ------------------------------------------------------------ */ - /** - * Controls logging of the request cookies. - * - * @param logCookies true - values of request cookies will be logged, - * false - values of request cookies will not be logged - */ - public void setLogCookies(boolean logCookies) - { - _logCookies = logCookies; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve log cookies flag - * - * @return value of the flag - */ - public boolean getLogCookies() - { - return _logCookies; - } - - /* ------------------------------------------------------------ */ - /** - * Controls logging of the request hostname. - * - * @param logServer true - request hostname will be logged, - * false - request hostname will not be logged - */ - public void setLogServer(boolean logServer) - { - _logServer = logServer; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve log hostname flag. - * - * @return value of the flag - */ - public boolean getLogServer() - { - return _logServer; - } - - /* ------------------------------------------------------------ */ - /** - * Controls logging of request processing time. - * - * @param logLatency true - request processing time will be logged - * false - request processing time will not be logged - */ - public void setLogLatency(boolean logLatency) - { - _logLatency = logLatency; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve log request processing time flag. - * - * @return value of the flag - */ - public boolean getLogLatency() - { - return _logLatency; - } - - /* ------------------------------------------------------------ */ - /** - * Controls whether the actual IP address of the connection or - * the IP address from the X-Forwarded-For header will be logged. - * - * @param preferProxiedForAddress true - IP address from header will be logged, - * false - IP address from the connection will be logged - */ - public void setPreferProxiedForAddress(boolean preferProxiedForAddress) - { - _preferProxiedForAddress = preferProxiedForAddress; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieved log X-Forwarded-For IP address flag. - * - * @return value of the flag - */ - public boolean getPreferProxiedForAddress() - { - return _preferProxiedForAddress; - } - /* ------------------------------------------------------------ */ /** * Set the log file name date format. @@ -430,210 +201,19 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog } /* ------------------------------------------------------------ */ - /** - * Controls logging of the request dispatch time - * - * @param value true - request dispatch time will be logged - * false - request dispatch time will not be logged - */ - public void setLogDispatch(boolean value) + @Override + public void write(String requestEntry) throws IOException { - _logDispatch = value; - } - - /* ------------------------------------------------------------ */ - /** - * Retrieve request dispatch time logging flag - * - * @return value of the flag - */ - public boolean isLogDispatch() - { - return _logDispatch; - } - - /* ------------------------------------------------------------ */ - /** - * Writes the request and response information to the output stream. - * - * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response) - */ - public void log(Request request, Response response) - { - try + synchronized(this) { - if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) + if (_writer==null) return; - - if (_fileOut == null) - return; - - StringBuilder buf= new StringBuilder(256); - - if (_logServer) - { - buf.append(request.getServerName()); - buf.append(' '); - } - - String addr = null; - if (_preferProxiedForAddress) - { - addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString()); - } - - if (addr == null) - addr = request.getRemoteAddr(); - - buf.append(addr); - buf.append(" - "); - Authentication authentication=request.getAuthentication(); - if (authentication instanceof Authentication.User) - buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName()); - else - buf.append(" - "); - - buf.append(" ["); - if (_logDateCache != null) - buf.append(_logDateCache.format(request.getTimeStamp())); - else - buf.append(request.getTimeStamp()); - - buf.append("] \""); - buf.append(request.getMethod()); - buf.append(' '); - buf.append(request.getUri().toString()); - buf.append(' '); - buf.append(request.getProtocol()); - buf.append("\" "); - if (request.getHttpChannelState().isInitial()) - { - int status = response.getStatus(); - if (status <= 0) - status = 404; - buf.append((char)('0' + ((status / 100) % 10))); - buf.append((char)('0' + ((status / 10) % 10))); - buf.append((char)('0' + (status % 10))); - } - else - buf.append("Async"); - - long responseLength = response.getLongContentLength(); - if (responseLength >= 0) - { - buf.append(' '); - if (responseLength > 99999) - buf.append(responseLength); - else - { - if (responseLength > 9999) - buf.append((char)('0' + ((responseLength / 10000) % 10))); - if (responseLength > 999) - buf.append((char)('0' + ((responseLength / 1000) % 10))); - if (responseLength > 99) - buf.append((char)('0' + ((responseLength / 100) % 10))); - if (responseLength > 9) - buf.append((char)('0' + ((responseLength / 10) % 10))); - buf.append((char)('0' + (responseLength) % 10)); - } - buf.append(' '); - } - else - buf.append(" - "); - - - if (_extended) - logExtended(request, response, buf); - - if (_logCookies) - { - Cookie[] cookies = request.getCookies(); - if (cookies == null || cookies.length == 0) - buf.append(" -"); - else - { - buf.append(" \""); - for (int i = 0; i < cookies.length; i++) - { - if (i != 0) - buf.append(';'); - buf.append(cookies[i].getName()); - buf.append('='); - buf.append(cookies[i].getValue()); - } - buf.append('\"'); - } - } - - if (_logDispatch || _logLatency) - { - long now = System.currentTimeMillis(); - - if (_logDispatch) - { - long d = request.getDispatchTime(); - buf.append(' '); - buf.append(now - (d==0 ? request.getTimeStamp():d)); - } - - if (_logLatency) - { - buf.append(' '); - buf.append(now - request.getTimeStamp()); - } - } - - buf.append(StringUtil.__LINE_SEPARATOR); - String log = buf.toString(); - synchronized(this) - { - if (_writer==null) - return; - _writer.write(log); - _writer.flush(); - } - } - catch (IOException e) - { - LOG.warn(e); - } - - } - - /* ------------------------------------------------------------ */ - /** - * Writes extended request and response information to the output stream. - * - * @param request request object - * @param response response object - * @param b StringBuilder to write to - * @throws IOException - */ - protected void logExtended(Request request, - Response response, - StringBuilder b) throws IOException - { - String referer = request.getHeader(HttpHeader.REFERER.toString()); - if (referer == null) - b.append("\"-\" "); - else - { - b.append('"'); - b.append(referer); - b.append("\" "); - } - - String agent = request.getHeader(HttpHeader.USER_AGENT.toString()); - if (agent == null) - b.append("\"-\" "); - else - { - b.append('"'); - b.append(agent); - b.append('"'); + _writer.write(requestEntry.toString()); + _writer.write(StringUtil.__LINE_SEPARATOR); + _writer.flush(); } } - + /* ------------------------------------------------------------ */ /** * Set up request logging and open log file. @@ -643,15 +223,9 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog @Override protected synchronized void doStart() throws Exception { - if (_logDateFormat != null) - { - _logDateCache = new DateCache(_logDateFormat,_logLocale); - _logDateCache.setTimeZoneID(_logTimeZone); - } - if (_filename != null) { - _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null); + _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null); _closeOut = true; LOG.info("Opened " + getDatedFilename()); } @@ -660,16 +234,10 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog _out = _fileOut; - if (_ignorePaths != null && _ignorePaths.length > 0) + synchronized(this) { - _ignorePathMap = new PathMap(); - for (int i = 0; i < _ignorePaths.length; i++) - _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]); + _writer = new OutputStreamWriter(_out); } - else - _ignorePathMap = null; - - _writer = new OutputStreamWriter(_out); super.doStart(); } @@ -707,7 +275,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog _out = null; _fileOut = null; _closeOut = false; - _logDateCache = null; _writer = null; } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index c91720e9cc4..4218daf54e6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -1779,6 +1779,9 @@ public class Request implements HttpServletRequest public void setHandled(boolean h) { _handled = h; + Response r=getResponse(); + if (_handled && r.getStatus()==0) + r.setStatus(200); } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index c9387ee5a63..e8e03113ade 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -280,10 +280,12 @@ public class Server extends HandlerWrapper implements Attributes @Override protected void doStart() throws Exception { - if (getStopAtShutdown()) { - ShutdownThread.register(this); - ShutdownMonitor.getInstance().start(); // initialize + if (getStopAtShutdown()) + { + ShutdownThread.register(this); } + + ShutdownMonitor.getInstance().start(); // initialize LOG.info("jetty-"+getVersion()); HttpGenerator.setServerVersion(getVersion()); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java index 405ce12f6fa..aba29959cdf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.util.thread.ShutdownThread; *

* Commands "stop" and "status" are currently supported. */ -public class ShutdownMonitor extends Thread +public class ShutdownMonitor { // Implementation of safe lazy init, using Initialization on Demand Holder technique. static class Holder @@ -53,11 +53,165 @@ public class ShutdownMonitor extends Thread return Holder.instance; } + /** + * ShutdownMonitorThread + * + * Thread for listening to STOP.PORT for command to stop Jetty. + * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be + * called after the stop. + * + */ + public class ShutdownMonitorThread extends Thread + { + + public ShutdownMonitorThread () + { + setDaemon(true); + setName("ShutdownMonitor"); + } + + @Override + public void run() + { + if (serverSocket == null) + { + return; + } + + while (serverSocket != null) + { + Socket socket = null; + try + { + socket = serverSocket.accept(); + + LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream())); + String receivedKey = lin.readLine(); + if (!key.equals(receivedKey)) + { + System.err.println("Ignoring command with incorrect key"); + continue; + } + + OutputStream out = socket.getOutputStream(); + + String cmd = lin.readLine(); + debug("command=%s",cmd); + if ("stop".equals(cmd)) + { + // Graceful Shutdown + debug("Issuing graceful shutdown.."); + ShutdownThread.getInstance().run(); + + // Reply to client + debug("Informing client that we are stopped."); + out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + + // Shutdown Monitor + debug("Shutting down monitor"); + close(socket); + socket = null; + close(serverSocket); + serverSocket = null; + + if (exitVm) + { + // Kill JVM + debug("Killing JVM"); + System.exit(0); + } + } + else if ("status".equals(cmd)) + { + // Reply to client + out.write("OK\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + } + } + catch (Exception e) + { + debug(e); + System.err.println(e.toString()); + } + finally + { + close(socket); + socket = null; + } + } + } + + public void start() + { + if (isAlive()) + { + if (DEBUG) + System.err.printf("ShutdownMonitorThread already started"); + return; // cannot start it again + } + + startListenSocket(); + + if (serverSocket == null) + { + return; + } + System.err.println("Starting ShutdownMonitorThread"); + super.start(); + } + + private void startListenSocket() + { + if (port < 0) + { + if (DEBUG) + System.err.println("ShutdownMonitor not in use (port < 0): " + port); + return; + } + + try + { + serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1")); + if (port == 0) + { + // server assigned port in use + port = serverSocket.getLocalPort(); + System.out.printf("STOP.PORT=%d%n",port); + } + + if (key == null) + { + // create random key + key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36); + System.out.printf("STOP.KEY=%s%n",key); + } + } + catch (Exception e) + { + debug(e); + System.err.println("Error binding monitor port " + port + ": " + e.toString()); + serverSocket = null; + } + finally + { + // establish the port and key that are in use + debug("STOP.PORT=%d",port); + debug("STOP.KEY=%s",key); + debug("%s",serverSocket); + } + } + + } + private boolean DEBUG; private int port; private String key; private boolean exitVm; private ServerSocket serverSocket; + private ShutdownMonitorThread thread; + + /** * Create a ShutdownMonitor using configuration from the System properties. @@ -75,7 +229,7 @@ public class ShutdownMonitor extends Thread // Use values passed thru via /jetty-start/ this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1")); - this.key = props.getProperty("STOP.KEY","eclipse"); + this.key = props.getProperty("STOP.KEY",null); this.exitVm = true; } @@ -149,77 +303,6 @@ public class ShutdownMonitor extends Thread return exitVm; } - @Override - public void run() - { - if (serverSocket == null) - { - return; - } - - while (serverSocket != null) - { - Socket socket = null; - try - { - socket = serverSocket.accept(); - - LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream())); - String key = lin.readLine(); - if (!this.key.equals(key)) - { - System.err.println("Ignoring command with incorrect key"); - continue; - } - - OutputStream out = socket.getOutputStream(); - - String cmd = lin.readLine(); - debug("command=%s",cmd); - if ("stop".equals(cmd)) - { - // Graceful Shutdown - debug("Issuing graceful shutdown.."); - ShutdownThread.getInstance().run(); - - // Reply to client - debug("Informing client that we are stopped."); - out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); - out.flush(); - - // Shutdown Monitor - debug("Shutting down monitor"); - close(socket); - socket = null; - close(serverSocket); - serverSocket = null; - - if (exitVm) - { - // Kill JVM - debug("Killing JVM"); - System.exit(0); - } - } - else if ("status".equals(cmd)) - { - // Reply to client - out.write("OK\r\n".getBytes(StringUtil.__UTF8)); - out.flush(); - } - } - catch (Exception e) - { - debug(e); - System.err.println(e.toString()); - } - finally - { - close(socket); - socket = null; - } - } - } public void setDebug(boolean flag) { @@ -228,90 +311,71 @@ public class ShutdownMonitor extends Thread public void setExitVm(boolean exitVm) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.exitVm = exitVm; } - this.exitVm = exitVm; } public void setKey(String key) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.key = key; } - this.key = key; } public void setPort(int port) { - if (isAlive()) + synchronized (this) { - throw new IllegalStateException("ShutdownMonitor already started"); - } - this.port = port; - } - - public void start() - { - if (isAlive()) - { - System.err.printf("ShutdownMonitor already started"); - return; // cannot start it again - } - startListenSocket(); - if (serverSocket == null) - { - return; - } - - super.start(); - } - - private void startListenSocket() - { - if (this.port < 0) - { - if (DEBUG) - System.err.println("ShutdownMonitor not in use (port < 0): " + port); - return; - } - - try - { - setDaemon(true); - setName("ShutdownMonitor"); - - this.serverSocket = new ServerSocket(this.port,1,InetAddress.getByName("127.0.0.1")); - if (this.port == 0) + if (thread != null && thread.isAlive()) { - // server assigned port in use - this.port = serverSocket.getLocalPort(); - System.out.printf("STOP.PORT=%d%n",this.port); + throw new IllegalStateException("ShutdownMonitorThread already started"); } - - if (this.key == null) - { - // create random key - this.key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36); - System.out.printf("STOP.KEY=%s%n",this.key); - } - } - catch (Exception e) - { - debug(e); - System.err.println("Error binding monitor port " + this.port + ": " + e.toString()); - } - finally - { - // establish the port and key that are in use - debug("STOP.PORT=%d",this.port); - debug("STOP.KEY=%s",this.key); - debug("%s",serverSocket); + this.port = port; } } + protected void start() throws Exception + { + ShutdownMonitorThread t = null; + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + System.err.printf("ShutdownMonitorThread already started"); + return; // cannot start it again + } + + thread = new ShutdownMonitorThread(); + t = thread; + } + + if (t != null) + t.start(); + } + + + protected boolean isAlive () + { + boolean result = false; + synchronized (this) + { + result = (thread != null && thread.isAlive()); + } + return result; + } + + @Override public String toString() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java new file mode 100644 index 00000000000..3c655d3116b --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java @@ -0,0 +1,69 @@ +// +// ======================================================================== +// 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.server; + +import java.io.IOException; + +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.log.Slf4jLog; + +/** + * Implementation of NCSARequestLog where output is sent as a SLF4J INFO Log message on the named logger "org.eclipse.jetty.server.RequestLog" + */ +@ManagedObject("NCSA standard format request log to slf4j bridge") +public class Slf4jRequestLog extends AbstractNCSARequestLog implements RequestLog +{ + private Slf4jLog logger; + private String loggerName; + + public Slf4jRequestLog() + { + // Default logger name (can be set) + this.loggerName = "org.eclipse.jetty.server.RequestLog"; + } + + public void setLoggerName(String loggerName) + { + this.loggerName = loggerName; + } + + public String getLoggerName() + { + return loggerName; + } + + @Override + protected boolean isEnabled() + { + return logger != null; + } + + @Override + public void write(String requestEntry) throws IOException + { + logger.info(requestEntry); + } + + @Override + protected synchronized void doStart() throws Exception + { + logger = new Slf4jLog(loggerName); + super.doStart(); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index ff7a2d9f96d..1954894aafe 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -19,12 +19,11 @@ package org.eclipse.jetty.server.session; import java.io.DataInputStream; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; -import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; @@ -37,6 +36,7 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.log.Logger; @@ -589,70 +589,56 @@ public class HashSessionManager extends AbstractSessionManager for (HashedSession session : _sessions.values()) session.save(reactivate); } + /* ------------------------------------------------------------ */ public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception { - /* - * Take care of this class's fields first by calling - * defaultReadObject - */ - DataInputStream in = new DataInputStream(is); - String clusterId = in.readUTF(); - in.readUTF(); // nodeId - long created = in.readLong(); - long accessed = in.readLong(); - int requests = in.readInt(); + DataInputStream di = new DataInputStream(is); + + String clusterId = di.readUTF(); + di.readUTF(); // nodeId + + long created = di.readLong(); + long accessed = di.readLong(); + int requests = di.readInt(); if (session == null) session = (HashedSession)newSession(created, accessed, clusterId); session.setRequests(requests); - int size = in.readInt(); + + int size = di.readInt(); + + restoreSessionAttributes(di, size, session); + + try + { + int maxIdle = di.readInt(); + session.setMaxInactiveInterval(maxIdle); + } + catch (EOFException e) + { + LOG.debug("No maxInactiveInterval persisted for session "+clusterId); + LOG.ignore(e); + } + + return session; + } + + + private void restoreSessionAttributes (InputStream is, int size, HashedSession session) + throws Exception + { if (size>0) { - ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in); + ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is); + for (int i=0; i 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); - } } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java index ce1b7318246..15e6022499c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java @@ -182,7 +182,6 @@ public class HashedSession extends AbstractSession */ //out.writeBoolean(_invalid); //out.writeBoolean(_doInvalidate); - //out.writeLong(_maxIdleMs); //out.writeBoolean( _newSession); out.writeInt(getRequests()); out.writeInt(getAttributes()); @@ -194,7 +193,8 @@ public class HashedSession extends AbstractSession oos.writeUTF(key); oos.writeObject(doGet(key)); } - oos.close(); + + out.writeInt(getMaxInactiveInterval()); } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java index 44f5620e43f..d81b4468ff6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -36,6 +36,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Random; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -74,6 +75,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager protected String _sessionIdTable = "JettySessionIds"; protected String _sessionTable = "JettySessions"; protected String _sessionTableRowId = "rowId"; + protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause protected Timer _timer; //scavenge timer protected TimerTask _task; //scavenge task @@ -86,7 +88,6 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager protected String _createSessionTable; protected String _selectBoundedExpiredSessions; - protected String _deleteOldExpiredSessions; protected String _insertId; protected String _deleteId; @@ -325,7 +326,17 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager { this._longType = longType; } + + public void setDeleteBlockSize (int bsize) + { + this._deleteBlockSize = bsize; + } + public int getDeleteBlockSize () + { + return this._deleteBlockSize; + } + public void setScavengeInterval (long sec) { if (sec<=0) @@ -430,7 +441,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager synchronized (_sessionIds) { if (LOG.isDebugEnabled()) - LOG.debug("Removing session id="+id); + LOG.debug("Removing sessionid="+id); try { _sessionIds.remove(id); @@ -568,22 +579,15 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager */ @Override public void doStart() + throws Exception { - try - { - initializeDatabase(); - prepareTables(); - cleanExpiredSessions(); - super.doStart(); - if (LOG.isDebugEnabled()) - LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); - _timer=new Timer("JDBCSessionScavenger", true); - setScavengeInterval(getScavengeInterval()); - } - catch (Exception e) - { - LOG.warn("Problem initialising JettySessionIds table", e); - } + initializeDatabase(); + prepareTables(); + super.doStart(); + if (LOG.isDebugEnabled()) + LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); + _timer=new Timer("JDBCSessionScavenger", true); + setScavengeInterval(getScavengeInterval()); } /** @@ -637,9 +641,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager throws SQLException { _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))"; - _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?"; + _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where lastNode = ? and expiryTime >= ? and expiryTime <= ?"; _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; - _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; _insertId = "insert into "+_sessionIdTable+" (id) values (?)"; _deleteId = "delete from "+_sessionIdTable+" where id = ?"; @@ -857,57 +860,79 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager private void scavenge () { Connection connection = null; - List expiredSessionIds = new ArrayList(); + Set expiredSessionIds = new HashSet(); try { if (LOG.isDebugEnabled()) - LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); + LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis()); if (_lastScavengeTime > 0) { connection = getConnection(); connection.setAutoCommit(true); - //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime"; + + + //Pass 1: find sessions for which we were last managing node that have just expired since last pass PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions); long lowerBound = (_lastScavengeTime - _scavengeIntervalMs); long upperBound = _lastScavengeTime; if (LOG.isDebugEnabled()) - LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound); + LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound); - statement.setLong(1, lowerBound); - statement.setLong(2, upperBound); + statement.setString(1, getWorkerName()); + statement.setLong(2, lowerBound); + statement.setLong(3, upperBound); ResultSet result = statement.executeQuery(); while (result.next()) { String sessionId = result.getString("sessionId"); expiredSessionIds.add(sessionId); - if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId); + if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId); } + result.close(); + scavengeSessions(expiredSessionIds, false); + - //tell the SessionManagers to expire any sessions with a matching sessionId in memory - Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); - for (int i=0; contexts!=null && i 0) + { + if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound); + selectExpiredSessions.setLong(1, upperBound); + result = selectExpiredSessions.executeQuery(); + while (result.next()) + { + String sessionId = result.getString("sessionId"); + String lastNode = result.getString("lastNode"); + if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode))) + expiredSessionIds.add(sessionId); + if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName()); + } + result.close(); + scavengeSessions(expiredSessionIds, false); + } + + + //Pass 3: + //find all sessions that have expired at least a couple of scanIntervals ago + //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then + //they are simply deleted + upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs); + expiredSessionIds.clear(); + if (upperBound > 0) { - if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound); - statement = connection.prepareStatement(_deleteOldExpiredSessions); - statement.setLong(1, upperBound); - int rows = statement.executeUpdate(); - if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound); + if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound); + selectExpiredSessions.setLong(1, upperBound); + result = selectExpiredSessions.executeQuery(); + while (result.next()) + { + String sessionId = result.getString("sessionId"); + expiredSessionIds.add(sessionId); + if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId); + } + result.close(); + scavengeSessions(expiredSessionIds, true); } } } @@ -921,12 +946,12 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager finally { _lastScavengeTime=System.currentTimeMillis(); - if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime); + if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime); if (connection != null) { try { - connection.close(); + connection.close(); } catch (SQLException e) { @@ -936,98 +961,133 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager } } + /** - * Get rid of sessions and sessionids from sessions that have already expired - * @throws Exception + * @param expiredSessionIds */ - private void cleanExpiredSessions () - throws Exception - { - Connection connection = null; - List expiredSessionIds = new ArrayList(); - try - { - connection = getConnection(); - connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - connection.setAutoCommit(false); - - PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions); - long now = System.currentTimeMillis(); - if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now); - - statement.setLong(1, now); - ResultSet result = statement.executeQuery(); - while (result.next()) + private void scavengeSessions (Set expiredSessionIds, boolean forceDelete) + { + Set remainingIds = new HashSet(expiredSessionIds); + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds); + if (successfullyExpiredIds != null) + remainingIds.removeAll(successfullyExpiredIds); + } } } - catch (Exception e) - { - if (connection != null) - connection.rollback(); - throw e; - } - finally + + //Any remaining ids are of those sessions that no context removed + if (!remainingIds.isEmpty() && forceDelete) { + LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds); try { - if (connection != null) - connection.close(); + //ensure they aren't in the local list of in-use session ids + synchronized (_sessionIds) + { + _sessionIds.removeAll(remainingIds); + } + + cleanExpiredSessionIds(remainingIds); } - catch (SQLException e) + catch (Exception e) { - LOG.warn(e); + LOG.warn("Error removing expired session ids", e); } } } + + + + + private void cleanExpiredSessionIds (Set expiredIds) + throws Exception + { + if (expiredIds == null || expiredIds.isEmpty()) + return; + + String[] ids = expiredIds.toArray(new String[expiredIds.size()]); + Connection con = null; + try + { + con = getConnection(); + con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + con.setAutoCommit(false); + + int start = 0; + int end = 0; + int blocksize = _deleteBlockSize; + int block = 0; + + while (end < ids.length) + { + start = block*blocksize; + if ((ids.length - start) >= blocksize) + end = start + blocksize; + else + end = ids.length; + + Statement statement = con.createStatement(); + //take them out of the sessionIds table + statement.executeUpdate(fillInClause("delete from "+_sessionIdTable+" where id in ", ids, start, end)); + //take them out of the sessions table + statement.executeUpdate(fillInClause("delete from "+_sessionTable+" where sessionId in ", ids, start, end)); + block++; + } + con.commit(); + + } + catch (Exception e) + { + if (con != null) + { + con.rollback(); + throw e; + } + } + finally + { + if (con != null) + { + con.close(); + } + } + } + /** * * @param sql - * @param connection - * @param expiredSessionIds + * @param atoms * @throws Exception */ - private String createCleanExpiredSessionsSql (String sql,Collection expiredSessionIds) + private String fillInClause (String sql, String[] literals, int start, int end) throws Exception { StringBuffer buff = new StringBuffer(); buff.append(sql); buff.append("("); - Iterator itor = expiredSessionIds.iterator(); - while (itor.hasNext()) + for (int i=start; i 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); - } - } - } - - /** * Set the time in seconds which is the interval between * saving the session access time to the database. @@ -552,7 +522,7 @@ public class JDBCSessionManager extends AbstractSessionManager session.setLastNode(getSessionIdManager().getWorkerName()); _sessions.put(idInCluster, session); - //update in db: if unable to update, session will be scavenged later + //update in db try { updateSessionNode(session); @@ -567,6 +537,9 @@ public class JDBCSessionManager extends AbstractSessionManager else { LOG.debug("getSession ({}): Session has expired", idInCluster); + //ensure that the session id for the expired session is deleted so that a new session with the + //same id cannot be created (because the idInUse() test would succeed) + _jdbcSessionIdMgr.removeSession(idInCluster); session=null; } @@ -583,6 +556,7 @@ public class JDBCSessionManager extends AbstractSessionManager return session; } } + /** * Get the number of sessions. @@ -779,8 +753,8 @@ public class JDBCSessionManager extends AbstractSessionManager synchronized (this) { - //take this session out of the map of sessions for this context - if (getSession(session.getClusterId()) != null) + //take this session out of the map of sessions for this context + if (_sessions.containsKey(session.getClusterId())) { removed = true; removeSession(session.getClusterId()); @@ -815,19 +789,20 @@ public class JDBCSessionManager extends AbstractSessionManager * * @param sessionIds */ - protected void expire (List sessionIds) + protected Set expire (Set sessionIds) { //don't attempt to scavenge if we are shutting down if (isStopping() || isStopped()) - return; + return null; - //Remove any sessions we already have in memory that match the ids + Thread thread=Thread.currentThread(); ClassLoader old_loader=thread.getContextClassLoader(); - ListIterator itor = sessionIds.listIterator(); - + + Set successfullyExpiredIds = new HashSet(); try { + Iterator itor = sessionIds.iterator(); while (itor.hasNext()) { String sessionId = (String)itor.next(); @@ -835,29 +810,49 @@ public class JDBCSessionManager extends AbstractSessionManager LOG.debug("Expiring session id "+sessionId); Session session = (Session)_sessions.get(sessionId); + + //if session is not in our memory, then fetch from db so we can call the usual listeners on it + if (session == null) + { + if (LOG.isDebugEnabled())LOG.debug("Force loading session id "+sessionId); + session = loadSession(sessionId, canonicalize(_context.getContextPath()), getVirtualHost(_context)); + if (session != null) + { + //loaded an expired session last managed on this node for this context, add it to the list so we can + //treat it like a normal expired session + synchronized (this) + { + _sessions.put(session.getClusterId(), session); + } + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Unrecognized session id="+sessionId); + continue; + } + } + if (session != null) { session.timeout(); - itor.remove(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Unrecognized session id="+sessionId); + successfullyExpiredIds.add(session.getClusterId()); } } + return successfullyExpiredIds; } catch (Throwable t) { LOG.warn("Problem expiring sessions", t); + return successfullyExpiredIds; } finally { thread.setContextClassLoader(old_loader); } } - - + + /** * Load a session from the database * @param id @@ -913,6 +908,9 @@ public class JDBCSessionManager extends AbstractSessionManager if (LOG.isDebugEnabled()) LOG.debug("LOADED session "+session); } + else + if (LOG.isDebugEnabled()) + LOG.debug("Failed to load session "+id); _reference.set(session); } catch (Exception e) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java index e5d9e104a6e..00358ca8755 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.server; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; @@ -39,6 +39,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.annotation.Stress; +import org.eclipse.jetty.toolchain.test.PropertyFlag; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -95,7 +96,14 @@ public class AsyncStressTest @Stress("High connection count") public void testAsync() throws Throwable { - doConnections(1600,240); + if (PropertyFlag.isEnabled("test.stress")) + { + doConnections(1600,240); + } + else + { + doConnections(80,80); + } } private void doConnections(int connections,final int loops) throws Throwable diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java new file mode 100644 index 00000000000..4d3714b997d --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java @@ -0,0 +1,85 @@ +// +// ======================================================================== +// 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.server; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.Socket; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.Test; + +public class HalfCloseRaceTest +{ + @Test + public void testHalfCloseRace() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + connector.setIdleTimeout(500); + server.addConnector(connector); + TestHandler handler = new TestHandler(); + server.setHandler(handler); + + server.start(); + + Socket client = new Socket("localhost",connector.getLocalPort()); + + int in = client.getInputStream().read(); + assertEquals(-1,in); + + client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes()); + + Thread.sleep(200); + assertEquals(0,handler.getHandled()); + + } + + public static class TestHandler extends AbstractHandler + { + transient int handled; + + public TestHandler() + { + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + handled++; + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().println("

Test

"); + } + + public int getHandled() + { + return handled; + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java index 662f1875ce7..815592d8743 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java @@ -49,8 +49,13 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest @Parameterized.Parameters public static Collection data() { - Object[][] data = new Object[][]{{HttpVersion.HTTP_1_0.asString(), true}, {HttpVersion.HTTP_1_0.asString(), - false}, {HttpVersion.HTTP_1_1.asString(), true}, {HttpVersion.HTTP_1_1.asString(), false}}; + Object[][] data = new Object[][] + { + {HttpVersion.HTTP_1_0.asString(), true}, + {HttpVersion.HTTP_1_1.asString(), true}, + {HttpVersion.HTTP_1_0.asString(), false}, + {HttpVersion.HTTP_1_1.asString(), false} + }; return Arrays.asList(data); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java index de5fbb73132..ca1c4ee6126 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java @@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.toolchain.test.PropertyFlag; import org.eclipse.jetty.util.IO; import org.junit.After; import org.junit.Before; @@ -42,7 +43,7 @@ import org.junit.Before; public class HttpServerTestFixture { // Useful constants protected static final long PAUSE=10L; - protected static final int LOOPS=50; + protected static final int LOOPS=PropertyFlag.isEnabled("test.stress")?250:50; protected Server _server; protected URI _serverURI; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java index 4867016204d..9e290ab2ec3 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java @@ -234,27 +234,19 @@ public class PartialRFC2616Test } @Test - public void test3_9() + public void test3_9() throws Exception { - try - { - HttpFields fields=new HttpFields(); + HttpFields fields=new HttpFields(); - fields.put("Q","bbb;q=0.5,aaa,ccc;q=0.002,d;q=0,e;q=0.0001,ddd;q=0.001,aa2,abb;q=0.7"); - Enumeration qualities=fields.getValues("Q",", \t"); - List list=HttpFields.qualityList(qualities); - assertEquals("Quality parameters","aaa",HttpFields.valueParameters(list.get(0),null)); - assertEquals("Quality parameters","aa2",HttpFields.valueParameters(list.get(1),null)); - assertEquals("Quality parameters","abb",HttpFields.valueParameters(list.get(2),null)); - assertEquals("Quality parameters","bbb",HttpFields.valueParameters(list.get(3),null)); - assertEquals("Quality parameters","ccc",HttpFields.valueParameters(list.get(4),null)); - assertEquals("Quality parameters","ddd",HttpFields.valueParameters(list.get(5),null)); - } - catch (Exception e) - { - e.printStackTrace(); - assertTrue(false); - } + fields.put("Q","bbb;q=0.5,aaa,ccc;q=0.002,d;q=0,e;q=0.0001,ddd;q=0.001,aa2,abb;q=0.7"); + Enumeration qualities=fields.getValues("Q",", \t"); + List list=HttpFields.qualityList(qualities); + assertEquals("Quality parameters","aaa",HttpFields.valueParameters(list.get(0),null)); + assertEquals("Quality parameters","aa2",HttpFields.valueParameters(list.get(1),null)); + assertEquals("Quality parameters","abb",HttpFields.valueParameters(list.get(2),null)); + assertEquals("Quality parameters","bbb",HttpFields.valueParameters(list.get(3),null)); + assertEquals("Quality parameters","ccc",HttpFields.valueParameters(list.get(4),null)); + assertEquals("Quality parameters","ddd",HttpFields.valueParameters(list.get(5),null)); } @Test diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java new file mode 100644 index 00000000000..596f9ccc2bb --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// 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.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; + +import org.junit.Test; + +/** + * ShutdownMonitorTest + * + * + * + */ +public class ShutdownMonitorTest +{ + + + @Test + public void testShutdown () + throws Exception + { + + //test port and key assignment + ShutdownMonitor.getInstance().setPort(0); + ShutdownMonitor.getInstance().setExitVm(false); + ShutdownMonitor.getInstance().start(); + String key = ShutdownMonitor.getInstance().getKey(); + int port = ShutdownMonitor.getInstance().getPort(); + + //try starting a 2nd time (should be ignored) + ShutdownMonitor.getInstance().start(); + + + stop(port,key,true); + assertTrue(!ShutdownMonitor.getInstance().isAlive()); + + //should be able to change port and key because it is stopped + ShutdownMonitor.getInstance().setPort(0); + ShutdownMonitor.getInstance().setKey("foo"); + ShutdownMonitor.getInstance().start(); + + key = ShutdownMonitor.getInstance().getKey(); + port = ShutdownMonitor.getInstance().getPort(); + assertTrue(ShutdownMonitor.getInstance().isAlive()); + + stop(port,key,true); + assertTrue(!ShutdownMonitor.getInstance().isAlive()); + } + + + public void stop (int port, String key, boolean check) + throws Exception + { + Socket s = null; + + try + { + //send stop command + s = new Socket(InetAddress.getByName("127.0.0.1"),port); + + OutputStream out = s.getOutputStream(); + out.write((key + "\r\nstop\r\n").getBytes()); + out.flush(); + + if (check) + { + //wait a little + Thread.currentThread().sleep(600); + + //check for stop confirmation + LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream())); + String response; + if ((response = lin.readLine()) != null) + { + assertEquals("Stopped", response); + } + else + throw new IllegalStateException("No stop confirmation"); + } + } + finally + { + if (s != null) s.close(); + } + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java index 1698166bc25..1de5bc71b59 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.annotation.Stress; +import org.eclipse.jetty.toolchain.test.PropertyFlag; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -130,35 +131,37 @@ public class StressTest } @Test - @Stress("Much threading") public void testNonPersistent() throws Throwable { // TODO needs to be further investigated - assumeTrue(!OS.IS_OSX); + assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress")); doThreads(10,10,false); - Thread.sleep(1000); - doThreads(20,20,false); - Thread.sleep(1000); - doThreads(200,10,false); - Thread.sleep(1000); - doThreads(200,200,false); + if (PropertyFlag.isEnabled("test.stress")) + { + doThreads(20,20,false); + Thread.sleep(1000); + doThreads(200,10,false); + Thread.sleep(1000); + doThreads(200,200,false); + } } @Test - @Stress("Much threading") public void testPersistent() throws Throwable { // TODO needs to be further investigated - assumeTrue(!OS.IS_OSX); + assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress")); doThreads(10,10,true); - Thread.sleep(1000); - doThreads(40,40,true); - Thread.sleep(1000); - doThreads(200,10,true); - Thread.sleep(1000); - doThreads(200,200,true); + if (PropertyFlag.isEnabled("test.stress")) + { + doThreads(40,40,true); + Thread.sleep(1000); + doThreads(200,10,true); + Thread.sleep(1000); + doThreads(200,200,true); + } } private void doThreads(int threadCount, final int loops, final boolean persistent) throws Throwable diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java index 12825dbf595..11ad5a90f52 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java @@ -22,7 +22,7 @@ import java.io.File; import junit.framework.Assert; -import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.Request; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; @@ -71,7 +71,7 @@ public class HashSessionManagerTest } - @Test + @Test public void testValidSessionIdRemoval() throws Exception { final HashSessionManager manager = new HashSessionManager(); @@ -90,4 +90,49 @@ public class HashSessionManagerTest Assert.assertTrue("File shouldn't exist!", !new File(testDir,"validFile.session").exists()); } + + @Test + public void testHashSession() throws Exception + { + File testDir = MavenTestingUtils.getTargetTestingDir("saved"); + testDir.mkdirs(); + HashSessionManager manager = new HashSessionManager(); + manager.setStoreDirectory(testDir); + manager.setMaxInactiveInterval(5); + Assert.assertTrue(testDir.exists()); + Assert.assertTrue(testDir.canWrite()); + + HashSessionIdManager idManager = new HashSessionIdManager(); + idManager.setWorkerName("foo"); + manager.setSessionIdManager(idManager); + + idManager.start(); + manager.start(); + + HashedSession session = (HashedSession)manager.newHttpSession(new Request(null, null)); + String sessionId = session.getId(); + + session.setAttribute("one", new Integer(1)); + session.setAttribute("two", new Integer(2)); + + //stop will persist sessions + idManager.stop(); + manager.setMaxInactiveInterval(30); //change max inactive interval for *new* sessions + manager.stop(); + + Assert.assertTrue("File should exist!", new File(testDir, session.getId()).exists()); + + //start will restore sessions + idManager.start(); + manager.start(); + + HashedSession restoredSession = (HashedSession)manager.getSession(sessionId); + Assert.assertNotNull(restoredSession); + + Object o = restoredSession.getAttribute("one"); + Assert.assertNotNull(o); + + Assert.assertEquals(1, ((Integer)o).intValue()); + Assert.assertEquals(5, restoredSession.getMaxInactiveInterval()); + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java index 6592a3aa68c..4a8945673ef 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java @@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSocket; @@ -866,7 +865,62 @@ public class SslBytesServerTest extends SslBytesTest // Close the raw socket, this generates a truncation attack proxy.flushToServer(null); - // Expect raw close from server + // Expect alert + raw close from server + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + + // Check that we did not spin + TimeUnit.MILLISECONDS.sleep(500); + Assert.assertThat(sslFills.get(), Matchers.lessThan(20)); + Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20)); + Assert.assertThat(httpParses.get(), Matchers.lessThan(20)); + + client.close(); + } + + @Test + public void testRequestWithImmediateRawClose() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + @Override + public Object call() throws Exception + { + OutputStream clientOutput = client.getOutputStream(); + clientOutput.write(("" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n").getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Application data + TLSRecord record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToServer(record, 0); + // Close the raw socket, this generates a truncation attack + proxy.flushToServer(null); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Application data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + // Expect alert + raw close from server + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); record = proxy.readFromServer(); Assert.assertNull(String.valueOf(record), record); proxy.flushToClient(record); @@ -1634,9 +1688,6 @@ public class SslBytesServerTest extends SslBytesTest Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20)); Assert.assertThat(httpParses.get(), Matchers.lessThan(50)); - //System.err.println(((Dumpable)server.getConnectors()[0]).dump()); - Assert.assertThat(((Dumpable)server.getConnectors()[0]).dump(), Matchers.containsString("SCEP@")); - completeClose(client); TimeUnit.MILLISECONDS.sleep(200); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java new file mode 100644 index 00000000000..30109111fc3 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java @@ -0,0 +1,124 @@ +// +// ======================================================================== +// 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.servlet; + +import static org.hamcrest.Matchers.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URI; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.IO; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class RequestHeadersTest +{ + @SuppressWarnings("serial") + private static class RequestHeaderServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setContentType("text/plain"); + PrintWriter out = resp.getWriter(); + out.printf("X-Camel-Type = %s",req.getHeader("X-Camel-Type")); + } + } + + private static Server server; + private static ServerConnector connector; + private static URI serverUri; + + @BeforeClass + public static void startServer() throws Exception + { + // Configure Server + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + context.addServlet(new ServletHolder(new RequestHeaderServlet()),"/*"); + + // Start Server + server.start(); + + String host = connector.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = connector.getLocalPort(); + serverUri = new URI(String.format("http://%s:%d/",host,port)); + } + + @AfterClass + public static void stopServer() + { + try + { + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(System.err); + } + } + + @Test + public void testGetLowercaseHeader() throws IOException + { + HttpURLConnection http = null; + try + { + http = (HttpURLConnection)serverUri.toURL().openConnection(); + // Set header in all lowercase + http.setRequestProperty("x-camel-type","bactrian"); + + try (InputStream in = http.getInputStream()) + { + String resp = IO.toString(in, "UTF-8"); + Assert.assertThat("Response", resp, is("X-Camel-Type = bactrian")); + } + } + finally + { + if (http != null) + { + http.disconnect(); + } + } + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index 33c7bda590c..392d4e5138c 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -130,8 +130,11 @@ public class DoSFilter implements Filter { private static final Logger LOG = Log.getLogger(DoSFilter.class); - private static final Pattern IP_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"); - private static final Pattern CIDR_PATTERN = Pattern.compile(IP_PATTERN + "/(\\d{1,2})"); + private static final String IPv4_GROUP = "(\\d{1,3})"; + private static final Pattern IPv4_PATTERN = Pattern.compile(IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP); + private static final String IPv6_GROUP = "(\\p{XDigit}{1,4})"; + private static final Pattern IPv6_PATTERN = Pattern.compile(IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP); + private static final Pattern CIDR_PATTERN = Pattern.compile("([^/]+)/(\\d+)"); private static final String __TRACKER = "DoSFilter.Tracker"; private static final String __THROTTLED = "DoSFilter.Throttled"; @@ -625,31 +628,94 @@ public class DoSFilter implements Filter return false; } - protected boolean subnetMatch(String subnetAddress, String candidate) + protected boolean subnetMatch(String subnetAddress, String address) { - Matcher matcher = CIDR_PATTERN.matcher(subnetAddress); - int subnet = intFromAddress(matcher); - int prefix = Integer.parseInt(matcher.group(5)); - // Sets the most significant prefix bits to 1 - // If prefix == 8 => 11111111_00000000_00000000_00000000 - int mask = ~((1 << (32 - prefix)) - 1); - int ip = intFromAddress(IP_PATTERN.matcher(candidate)); - return (ip & mask) == (subnet & mask); + Matcher cidrMatcher = CIDR_PATTERN.matcher(subnetAddress); + if (!cidrMatcher.matches()) + return false; + + String subnet = cidrMatcher.group(1); + int prefix; + try + { + prefix = Integer.parseInt(cidrMatcher.group(2)); + } + catch (NumberFormatException x) + { + LOG.info("Ignoring malformed CIDR address {}", subnetAddress); + return false; + } + + byte[] subnetBytes = addressToBytes(subnet); + if (subnetBytes == null) + { + LOG.info("Ignoring malformed CIDR address {}", subnetAddress); + return false; + } + byte[] addressBytes = addressToBytes(address); + if (addressBytes == null) + { + LOG.info("Ignoring malformed remote address {}", address); + return false; + } + + // Comparing IPv4 with IPv6 ? + int length = subnetBytes.length; + if (length != addressBytes.length) + return false; + + byte[] mask = prefixToBytes(prefix, length); + + for (int i = 0; i < length; ++i) + { + if ((subnetBytes[i] & mask[i]) != (addressBytes[i] & mask[i])) + return false; + } + + return true; } - private int intFromAddress(Matcher matcher) + private byte[] addressToBytes(String address) { - int result = 0; - if (matcher.matches()) + Matcher ipv4Matcher = IPv4_PATTERN.matcher(address); + if (ipv4Matcher.matches()) { - for (int i = 0; i < 4; ++i) - { - int b = Integer.parseInt(matcher.group(i + 1)); - result |= b << 8 * (3 - i); - } + byte[] result = new byte[4]; + for (int i = 0; i < result.length; ++i) + result[i] = Integer.valueOf(ipv4Matcher.group(i + 1)).byteValue(); return result; } - throw new IllegalStateException(); + else + { + Matcher ipv6Matcher = IPv6_PATTERN.matcher(address); + if (ipv6Matcher.matches()) + { + byte[] result = new byte[16]; + for (int i = 0; i < result.length; i += 2) + { + int word = Integer.valueOf(ipv6Matcher.group(i / 2 + 1), 16); + result[i] = (byte)((word & 0xFF00) >>> 8); + result[i + 1] = (byte)(word & 0xFF); + } + return result; + } + } + return null; + } + + private byte[] prefixToBytes(int prefix, int length) + { + byte[] result = new byte[length]; + int index = 0; + while (prefix / 8 > 0) + { + result[index] = -1; + prefix -= 8; + ++index; + } + // Sets the _prefix_ most significant bits to 1 + result[index] = (byte)~((1 << (8 - prefix)) - 1); + return result; } public void destroy() @@ -980,14 +1046,7 @@ public class DoSFilter implements Filter private boolean addWhitelistAddress(List list, String address) { address = address.trim(); - if (address.length() > 0) - { - if (CIDR_PATTERN.matcher(address).matches() || IP_PATTERN.matcher(address).matches()) - return list.add(address); - else - LOG.warn("Ignoring malformed whitelist IP address {}", address); - } - return false; + return address.length() > 0 && list.add(address); } /** diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java index 3a25f68e763..31c00d4efcd 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java @@ -80,12 +80,17 @@ public class DoSFilterTest extends AbstractDoSFilterTest List whitelist = new ArrayList(); whitelist.add("192.168.0.1"); whitelist.add("10.0.0.0/8"); + whitelist.add("4d8:0:a:1234:ABc:1F:b18:17"); + whitelist.add("4d8:0:a:1234:ABc:1F:0:0/96"); Assert.assertTrue(filter.checkWhitelist(whitelist, "192.168.0.1")); Assert.assertFalse(filter.checkWhitelist(whitelist, "192.168.0.2")); Assert.assertFalse(filter.checkWhitelist(whitelist, "11.12.13.14")); Assert.assertTrue(filter.checkWhitelist(whitelist, "10.11.12.13")); Assert.assertTrue(filter.checkWhitelist(whitelist, "10.0.0.0")); Assert.assertFalse(filter.checkWhitelist(whitelist, "0.0.0.0")); + Assert.assertTrue(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1F:b18:17")); + Assert.assertTrue(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1F:b18:0")); + Assert.assertFalse(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1D:0:0")); } private boolean hitRateTracker(DoSFilter doSFilter, int sleep) throws InterruptedException diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java index e7d43dc408c..ed75ec4414e 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java @@ -119,7 +119,7 @@ public class EventSourceServletTest Assert.assertEquals("data: " + data, received); socket.close(); - Assert.assertTrue(closeLatch.await(heartBeatPeriod * 2, TimeUnit.SECONDS)); + Assert.assertTrue(closeLatch.await(heartBeatPeriod * 3, TimeUnit.SECONDS)); } @Test diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml index 0a70625a2bb..0a237216da0 100644 --- a/jetty-spdy/pom.xml +++ b/jetty-spdy/pom.xml @@ -13,10 +13,84 @@ Jetty :: SPDY :: Parent - 1.1.5.v20130313 1.1.0.v20120525 + + + 7u9 + + + java.version + 1.7.0_9 + + + + 1.1.3.v20130313 + + + + 7u10 + + + java.version + 1.7.0_10 + + + + 1.1.3.v20130313 + + + + 7u11 + + + java.version + 1.7.0_11 + + + + 1.1.3.v20130313 + + + + 7u13 + + + java.version + 1.7.0_13 + + + + 1.1.4.v20130313 + + + + 7u15 + + + java.version + 1.7.0_15 + + + + 1.1.5.v20130313 + + + + 7u17 + + + java.version + 1.7.0_17 + + + + 1.1.5.v20130313 + + + + spdy-core spdy-client diff --git a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml index 277f22e6095..feea74418c1 100644 --- a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml +++ b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml @@ -2,61 +2,18 @@ - - + - - + - - - - - - - - - - /etc/keystore - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - OBF:1u2u1wml1z7s1z7a1wnl1u2g - /etc/keystore - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - - - - SSL_RSA_WITH_DES_CBC_SHA - SSL_DHE_RSA_WITH_DES_CBC_SHA - SSL_DHE_DSS_WITH_DES_CBC_SHA - SSL_RSA_EXPORT_WITH_RC4_40_MD5 - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA - - - - - - - - - - - - - - - - - @@ -114,7 +71,7 @@ - + @@ -145,7 +102,7 @@ 3 - + @@ -154,21 +111,21 @@ 2 - + - + - + 30000 diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java new file mode 100644 index 00000000000..dd2f53c933d --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// 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.nio.ByteBuffer; + + +/* ------------------------------------------------------------ */ +/** Abstract Trie implementation. + *

Provides some common implementations, which may not be the most + * efficient. For byte operations, the assumption is made that the charset + * is ISO-8859-1

+ * @param + */ +public abstract class AbstractTrie implements Trie +{ + final boolean _caseInsensitive; + + protected AbstractTrie(boolean insensitive) + { + _caseInsensitive=insensitive; + } + + @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) + { + return get(s,0,s.length()); + } + + + @Override + public V get(ByteBuffer b, int offset, int len) + { + b=b.duplicate(); + b.position(b.position()+offset); + b.limit(b.position()+len); + return get(BufferUtil.toString(b,StringUtil.__ISO_8859_1_CHARSET)); + } + + @Override + public V get(ByteBuffer b) + { + return get(b,0,b.remaining()); + } + + @Override + public V getBest(String s) + { + return getBest(s,0,s.length()); + } + + @Override + public V getBest(byte[] b, int offset, int len) + { + return getBest(new String(b,offset,len,StringUtil.__ISO_8859_1_CHARSET)); + } + + @Override + public V getBest(ByteBuffer b, int offset, int len) + { + b=b.duplicate(); + b.position(b.position()+offset); + b.limit(b.position()+len); + return getBest(BufferUtil.toString(b,StringUtil.__ISO_8859_1_CHARSET)); + } + + @Override + public boolean isCaseInsensitive() + { + return _caseInsensitive; + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java index 73413232a0d..7c92ac5477e 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.util; -import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; @@ -28,7 +27,7 @@ import java.util.Set; * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache). * @param */ -public class ArrayTernaryTrie implements Trie +public class ArrayTernaryTrie extends AbstractTrie { private static int LO=1; private static int EQ=2; @@ -70,13 +69,37 @@ public class ArrayTernaryTrie implements Trie this(128); } + public ArrayTernaryTrie(boolean insensitive) + { + this(insensitive,128); + } + public ArrayTernaryTrie(int capacityInNodes) { + this(true,capacityInNodes); + } + + public ArrayTernaryTrie(boolean insensitive, int capacityInNodes) + { + super(insensitive); _value=new Object[capacityInNodes]; _tree=new char[capacityInNodes*ROW_SIZE]; _key=new String[capacityInNodes]; } - + + /* ------------------------------------------------------------ */ + /** Copy Trie and change capacity by a factor + * @param trie + * @param factor + */ + public ArrayTernaryTrie(ArrayTernaryTrie trie, double factor) + { + this(trie.isCaseInsensitive(),(int)(trie._value.length*factor)); + _rows=trie._rows; + System.arraycopy(trie._value,0,_value,0,trie._value.length); + System.arraycopy(trie._tree,0,_tree,0,trie._tree.length); + System.arraycopy(trie._key,0,_key,0,trie._key.length); + } /* ------------------------------------------------------------ */ @Override @@ -90,8 +113,8 @@ public class ArrayTernaryTrie implements Trie for(k=0; k < limit; k++) { char c=s.charAt(k); - if (c<128) - c=StringUtil.lowercases[c&0x7f]; + if(isCaseInsensitive() && c<128) + c=StringUtil.lowercases[c]; while (true) { @@ -132,67 +155,20 @@ public class ArrayTernaryTrie 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 length) { int t = _tree[EQ]; - int len = s.length(); - int node=0; - for(int i=0; t!=0 && i < len ; i++) + int len = length; + int i=0; + while(i implements Trie if (diff==0) { - node=t; + if (i==len) + return (V)_value[t]; t=_tree[row+EQ]; + if (t==0) + return null; break; } - if (diff<0) - t=_tree[row+LO]; - else - t=_tree[row+HI]; + t=_tree[row+((diff<0)?LO:HI)]; + if (t==0) + return null; } } - return (V)_value[node]; + return null; } + /* ------------------------------------------------------------ */ @Override - public V getBest(byte[] b,int offset,int len) + public V getBest(String s) { - return getBest(_tree[EQ],b,offset,len); - } - - /* ------------------------------------------------------------ */ - @Override - public V getBest(ByteBuffer b,int offset,int len) - { - if (b.hasArray()) - return getBest(_tree[EQ],b.array(),b.arrayOffset()+b.position()+offset,len); - return getBest(_tree[EQ],b,offset,len); + return getBest(_tree[EQ],s,0,s.length()); } - private V getBest(int t,byte[] b,int offset,int len) + /* ------------------------------------------------------------ */ + @Override + public V getBest(String s, int offset, int length) + { + return getBest(_tree[EQ],s,offset,length); + } + + /* ------------------------------------------------------------ */ + private V getBest(int t,String s,int offset,int len) { int node=0; for(int i=0; t!=0 && i implements Trie // if this node is a match, recurse to remember if (_key[node]!=null) { - V best=getBest(t,b,offset+i+1,len-i-1); + V best=getBest(t,s,offset+i+1,len-i-1); if (best!=null) return best; return (V)_value[node]; @@ -261,57 +241,12 @@ public class ArrayTernaryTrie implements Trie break; } - if (diff<0) - t=_tree[row+LO]; - else - t=_tree[row+HI]; + t=_tree[row+((diff<0)?LO:HI)]; } } return null; } - private V getBest(int t,ByteBuffer b,int offset,int len) - { - int pos=b.position()+offset; - int node=0; - for(int i=0; t!=0 && iThis implementation is always case insensitive and is optimal for + * a small number of fixed strings with few special characters. + *

* @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 c) + { + return drainTo(c, Integer.MAX_VALUE); + } + + @Override + public int drainTo(Collection 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 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