Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Wilkins2013-04-07 18:18:02 -0400
committerGreg Wilkins2013-04-07 18:18:02 -0400
commit574ec5831a8a278bec69e01f6ad293de64d4abe2 (patch)
tree339103e7e8d5e6ddbb499db163cca8a0795dcf9f
parent897c35c2cb556e2dd16e31e30a9b302576ba306a (diff)
parent120b8c98396f8176bf80798ed86db5a6568381c8 (diff)
downloadorg.eclipse.jetty.project-574ec5831a8a278bec69e01f6ad293de64d4abe2.tar.gz
org.eclipse.jetty.project-574ec5831a8a278bec69e01f6ad293de64d4abe2.tar.xz
org.eclipse.jetty.project-574ec5831a8a278bec69e01f6ad293de64d4abe2.zip
Merge branch 'master' into release-9
-rw-r--r--.travis.yml4
-rw-r--r--README.txt1
-rw-r--r--VERSION.txt68
-rw-r--r--examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java4
-rw-r--r--examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java4
-rw-r--r--jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java39
-rw-r--r--jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java130
-rw-r--r--jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java2
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java73
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java30
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java32
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java13
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java10
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java64
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java63
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java62
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java4
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java17
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java11
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java3
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java25
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java2
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java85
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java3
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java1
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java155
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java247
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java32
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java369
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java (renamed from jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java)164
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java (renamed from jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesTest.java)6
-rw-r--r--jetty-client/src/test/resources/keystorebin2201 -> 0 bytes
-rw-r--r--jetty-distribution/pom.xml27
-rwxr-xr-xjetty-distribution/src/main/resources/bin/jetty.sh68
-rw-r--r--jetty-distribution/src/main/resources/etc/jetty-started.xml15
-rw-r--r--jetty-distribution/src/main/resources/etc/jetty.conf7
-rw-r--r--jetty-distribution/src/main/resources/start.ini67
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java16
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java4
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java93
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java5
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java13
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java54
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java34
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java194
-rw-r--r--jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java25
-rw-r--r--jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java22
-rw-r--r--jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java4
-rw-r--r--jetty-jsp/pom.xml6
-rw-r--r--jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenAnnotationConfiguration.java2
-rw-r--r--jetty-monitor/README.txt13
-rw-r--r--jetty-monitor/pom.xml145
-rw-r--r--jetty-monitor/src/main/config/etc/jetty-monitor.xml30
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/JMXMonitor.java194
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java592
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java419
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java289
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java81
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ConsoleNotifier.java61
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventNotifier.java39
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventState.java207
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/EventTrigger.java74
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/LoggingNotifier.java65
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorAction.java179
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java119
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java119
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java172
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/SimpleAction.java46
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java34
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java202
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java168
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java134
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java239
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java89
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java91
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java91
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java91
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java91
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/OrEventTrigger.java138
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java100
-rw-r--r--jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java100
-rw-r--r--jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java525
-rw-r--r--jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java48
-rw-r--r--jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java163
-rw-r--r--jetty-monitor/src/test/resources/jetty-logging.properties3
-rw-r--r--jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java2
-rw-r--r--jetty-osgi/jetty-osgi-npn/pom.xml55
-rw-r--r--jetty-osgi/test-jetty-osgi/pom.xml2
-rw-r--r--jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java4
-rw-r--r--jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java9
-rw-r--r--jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java60
-rw-r--r--jetty-server/src/main/config/etc/jetty-https.xml51
-rw-r--r--jetty-server/src/main/config/etc/jetty-requestlog.xml4
-rw-r--r--jetty-server/src/main/config/etc/jetty-ssl.xml41
-rw-r--r--jetty-server/src/main/config/etc/jetty.xml12
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java501
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java129
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java11
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java19
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java469
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Request.java3
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Response.java14
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Server.java12
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java329
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java69
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java1
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java2
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java6
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java63
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java82
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java4
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java303
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java739
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java12
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java85
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java9
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java3
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java32
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java112
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java35
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java1
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java49
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java2
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java13
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java13
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java11
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java17
-rw-r--r--jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java654
-rw-r--r--jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java163
-rw-r--r--jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java124
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java118
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java37
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java9
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java4
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java5
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java2
-rw-r--r--jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java8
-rw-r--r--jetty-spdy/pom.xml76
-rw-r--r--jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java1
-rw-r--r--jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java24
-rw-r--r--jetty-spdy/spdy-http-server/pom.xml6
-rw-r--r--jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml45
-rw-r--r--jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java25
-rw-r--r--jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java25
-rw-r--r--jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java37
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java103
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java179
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java77
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java4
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java105
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueue.java418
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java570
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java71
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java9
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java1
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java23
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java41
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java44
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java7
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java80
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java5
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java19
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java67
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java23
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayBlockingQueueUnboundedTest.java166
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java172
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java9
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java28
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java37
-rw-r--r--jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java12
-rw-r--r--jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java2
-rw-r--r--jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java13
-rw-r--r--jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java2
-rw-r--r--jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java16
-rw-r--r--jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java42
-rw-r--r--jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java1
-rw-r--r--jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java2
-rw-r--r--jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java2
-rw-r--r--jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java9
-rw-r--r--jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java11
-rw-r--r--jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java5
-rw-r--r--jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java99
-rw-r--r--jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java163
-rw-r--r--jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java97
-rw-r--r--jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java62
-rw-r--r--jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java15
-rw-r--r--pom.xml3
-rw-r--r--tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java78
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java59
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ModifyMaxInactiveIntervalTest.java145
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java58
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java146
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java120
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/resources/foobar.jarbin0 -> 2531 bytes
-rw-r--r--tests/test-sessions/test-jdbc-sessions/src/test/resources/foobarNOfoo.jarbin0 -> 1971 bytes
-rw-r--r--tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java146
-rw-r--r--tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jarbin0 -> 5378 bytes
197 files changed, 13712 insertions, 2137 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..80b6f4b780
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: java
+jdk:
+ - openjdk7
+ - oraclejdk7
diff --git a/README.txt b/README.txt
index 9157b3eaa5..12c781192c 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/VERSION.txt b/VERSION.txt
index bf6e9fcf09..a6c48bbedc 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -39,6 +39,74 @@ jetty-9.0.0.v20130308 - 08 March 2013
+ 402757 WebSocket client module can't be used with WebSocket server
module in the same WAR
+jetty-8.1.10.v20130312 - 12 March 2013
+ + 376273 Early EOF because of SSL Protocol Error on
+ https://api-3t.paypal.com/nvp.
+ + 381521 allow compress methods to be configured
+ + 392129 fixed handling of timeouts after startAsync
+ + 394064 ensure that JarFile instances are closed on JarFileResource.release()
+ + 398649 ServletContextListener.contextDestroyed() is not called on
+ ContextHandler unregistration
+ + 399703 made encoding error handling consistent
+ + 399799 do not hold lock while calling invalidation listeners
+ + 399967 Shutdown hook calls destroy
+ + 400040 NullPointerException in HttpGenerator.prepareBuffers
+ + 400142 ConcurrentModificationException in JDBC SessionManger
+ + 400144 When loading a session fails the JDBCSessionManger produces duplicate
+ session IDs
+ + 400312 ServletContextListener.contextInitialized() is not called when added
+ in ServletContainerInitializer.onStartup
+ + 400457 Thread context classloader hierarchy not searched when finding
+ webapp's java:comp/env
+ + 400859 limit max size of writes from cached content
+ + 401211 Remove requirement for jetty-websocket.jar in WEB-INF/lib
+ + 401317 Make Safari 5.x websocket support minVersion level error more clear
+ + 401382 Prevent parseAvailable from parsing next chunk when previous has not
+ been consumed. Handle no content-type in chunked request.
+ + 401474 Performance problem in org.eclipse.jetty.annotation.AnnotationParser
+ + 401485 zip file closed exception
+ + 401531 StringIndexOutOfBoundsException for "/*" <url-pattern> of
+ <jsp-property-group> fix for multiple mappings to *.jsp
+ + 401908 Enhance DosFilter to allow dynamic configuration of attributes.
+ + 402048 org.eclipse.jetty.server.ShutdownMonitor doesn't stop after the jetty
+ server is stopped
+ + 402485 reseed secure random
+ + 402735 jetty.sh to support status which is == check
+ + 402833 Test harness for global error page and hide exception message from
+ reason string
+
+jetty-7.6.10.v20130312 - 12 March 2013
+ + 376273 Early EOF because of SSL Protocol Error on
+ https://api-3t.paypal.com/nvp.
+ + 381521 allow compress methods to be configured
+ + 394064 ensure that JarFile instances are closed on JarFileResource.release()
+ + 398649 ServletContextListener.contextDestroyed() is not called on
+ ContextHandler unregistration
+ + 399703 made encoding error handling consistent
+ + 399799 do not hold lock while calling invalidation listeners
+ + 399967 Shutdown hook calls destroy
+ + 400040 NullPointerException in HttpGenerator.prepareBuffers
+ + 400142 ConcurrentModificationException in JDBC SessionManger
+ + 400144 When loading a session fails the JDBCSessionManger produces duplicate
+ session IDs
+ + 400457 Thread context classloader hierarchy not searched when finding
+ webapp's java:comp/env
+ + 400859 limit max size of writes from cached content
+ + 401211 Remove requirement for jetty-websocket.jar in WEB-INF/lib
+ + 401317 Make Safari 5.x websocket support minVersion level error more clear
+ + 401382 Prevent parseAvailable from parsing next chunk when previous has not
+ been consumed. Handle no content-type in chunked request.
+ + 401474 Performance problem in org.eclipse.jetty.annotation.AnnotationParser
+ + 401531 StringIndexOutOfBoundsException for "/*" <url-pattern> of
+ <jsp-property-group> fix for multiple mappings to *.jsp
+ + 401908 Enhance DosFilter to allow dynamic configuration of attributes.
+ + 402048 org.eclipse.jetty.server.ShutdownMonitor doesn't stop after the jetty
+ server is stopped
+ + 402485 reseed secure random
+ + 402735 jetty.sh to support status which is == check
+ + 402833 Test harness for global error page and hide exception message from
+ reason string
+
jetty-9.0.0.RC2 - 24 February 2013
+ Fix etc/jetty.xml TimerScheduler typo that is preventing normal startup
+ Fix etc/jetty-https.xml ExcludeCipherSuites typo that prevents SSL startup
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 ec27c98fed..b30373e310 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 8171a947e2..c2aa948dbb 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 2f79754951..d7290cf417 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<URI> containerUris = new ArrayList<URI>();
- 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;
- }
-
- 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;
- }
- });
+ 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;
+ }
+ });
}
}
}
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 f49c07ecdb..daab409961 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<files.length;f++)
{
@@ -763,13 +765,14 @@ public class AnnotationParser
{
Resource res = dir.addPath(files[f]);
if (res.isDirectory())
- parse(res, resolver);
+ parseDir(res, resolver);
String name = res.getName();
if (name.endsWith(".class"))
{
if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
{
Resource r = Resource.newResource(res.getURL());
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
scanClass(r.getInputStream());
}
@@ -836,7 +839,7 @@ public class AnnotationParser
/**
- * Parse classes in the supplied url of jar files.
+ * Parse classes in the supplied uris.
*
* @param uris
* @param resolver
@@ -848,36 +851,18 @@ public class AnnotationParser
if (uris==null)
return;
- JarScanner scanner = new JarScanner()
+ for (URI uri:uris)
{
- @Override
- public void processEntry(URI jarUri, JarEntry entry)
+ try
{
- try
- {
- String name = entry.getName();
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
- {
- String shortName = name.replace('/', '.').substring(0,name.length()-6);
-
- if ((resolver == null)
- ||
- (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
- {
- Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);
- scanClass(clazz.getInputStream());
-
- }
- }
- }
- catch (Exception e)
- {
- LOG.warn("Problem processing jar entry "+entry, e);
- }
+ parse(uri, resolver);
}
+ catch (Exception e)
+ {
+ LOG.warn("Problem parsing classes from {}", uri);
+ }
+ }
- };
- scanner.scan(null, uris, true);
}
/**
@@ -891,10 +876,91 @@ public class AnnotationParser
{
if (uri == null)
return;
- URI[] uris = {uri};
- parse(uris, resolver);
+
+ Resource r = Resource.newResource(uri);
+ if (r.exists() && r.isDirectory())
+ {
+ parseDir(r, resolver);
+ return;
+ }
+
+ String fullname = r.toString();
+ if (fullname.endsWith(".jar"))
+ {
+ parseJar(r, resolver);
+ return;
+ }
+
+ if (fullname.endsWith(".class"))
+ {
+ scanClass(r.getInputStream());
+ return;
+ }
}
+
+
+ /**
+ * Parse a resource that is a jar file.
+ *
+ * @param jarResource
+ * @param resolver
+ * @throws Exception
+ */
+ public void parseJar (Resource jarResource, final ClassNameResolver resolver)
+ throws Exception
+ {
+ if (jarResource == null)
+ return;
+
+ URI uri = jarResource.getURI();
+
+ if (jarResource.toString().endsWith(".jar"))
+ {
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
+
+ //treat it as a jar that we need to open and scan all entries from
+ InputStream in = jarResource.getInputStream();
+ if (in==null)
+ return;
+
+ JarInputStream jar_in = new JarInputStream(in);
+ try
+ {
+ JarEntry entry = jar_in.getNextJarEntry();
+ while (entry!=null)
+ {
+ try
+ {
+ String name = entry.getName();
+ if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
+ {
+ String shortName = name.replace('/', '.').substring(0,name.length()-6);
+
+ if ((resolver == null)
+ ||
+ (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+ {
+ Resource clazz = Resource.newResource("jar:"+uri+"!/"+name);
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
+ scanClass(clazz.getInputStream());
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem processing jar entry "+entry, e);
+ }
+
+ entry = jar_in.getNextJarEntry();
+ }
+ }
+ finally
+ {
+ jar_in.close();
+ }
+ }
+ }
/**
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
index 06cd738579..bb3e322377 100644
--- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
@@ -66,7 +66,7 @@ public class AntWebInfConfiguration extends WebInfConfiguration
{
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-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index ccb15cf642..7b66d6f2b4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -35,33 +35,34 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class AuthenticationProtocolHandler implements ProtocolHandler
+public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
+ public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
- private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
+ private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
private final HttpClient client;
private final int maxContentLength;
private final ResponseNotifier notifier;
- public AuthenticationProtocolHandler(HttpClient client)
- {
- this(client, 4096);
- }
-
- public AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+ protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
{
this.client = client;
this.maxContentLength = maxContentLength;
this.notifier = new ResponseNotifier(client);
}
- @Override
- public boolean accept(Request request, Response response)
+ protected HttpClient getHttpClient()
{
- return response.getStatus() == 401;
+ return client;
}
+ protected abstract HttpHeader getAuthenticateHeader();
+
+ protected abstract HttpHeader getAuthorizationHeader();
+
+ protected abstract URI getAuthenticationURI(Request request);
+
@Override
public Response.Listener getResponseListener()
{
@@ -89,23 +90,24 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
return;
}
- List<WWWAuthenticate> wwwAuthenticates = parseWWWAuthenticate(response);
- if (wwwAuthenticates.isEmpty())
+ HttpHeader header = getAuthenticateHeader();
+ List<Authentication.HeaderInfo> 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<WWWAuthenticate> parseWWWAuthenticate(Response response)
+ private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
{
// TODO: these should be ordered by strength
- List<WWWAuthenticate> result = new ArrayList<>();
- List<String> values = Collections.list(response.getHeaders().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
+ List<Authentication.HeaderInfo> result = new ArrayList<>();
+ List<String> 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 a1b542c753..dd2c7bccdc 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<Connection> 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.
@@ -967,11 +966,12 @@ public class HttpClient extends ContainerLifeCycle
engine.setUseClientMode(true);
SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
HttpConnection connection = newHttpConnection(HttpClient.this, appEndPoint, destination);
appEndPoint.setConnection(connection);
- callback.promise.succeeded(connection);
+ callback.succeeded(connection);
return sslConnection;
}
@@ -979,7 +979,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 +988,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<Connection>
+ private class ConnectionCallback implements Promise<Connection>
{
private final HttpDestination destination;
private final Promise<Connection> promise;
@@ -1002,6 +1002,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<ContentDecoder.Factory>
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 28a3198d3b..ca61d22413 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;
@@ -190,25 +191,17 @@ public class HttpConnection extends AbstractConnection implements Connection
params.append("&");
}
- // Behave as a GET, adding the params to the path, if it's a POST with some content
- if (method == HttpMethod.POST && request.getContent() != null)
- method = HttpMethod.GET;
-
- switch (method)
+ // POST with no content, send parameters as body
+ if (method == HttpMethod.POST && request.getContent() == null)
{
- case GET:
- {
- path += "?";
- path += params.toString();
- request.path(path);
- break;
- }
- case POST:
- {
- request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
- request.content(new StringContentProvider(params.toString()));
- break;
- }
+ request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
+ request.content(new StringContentProvider(params.toString()));
+ }
+ else
+ {
+ path += "?";
+ path += params.toString();
+ request.path(path);
}
}
@@ -251,7 +244,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 3bbbaa2dde..6967fd7e37 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 ba579a5b68..17835baa47 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 0000000000..841c661414
--- /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 0000000000..21c7c0be98
--- /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 aeb7dd4ac8..9341d7b1eb 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.
* <p />
* {@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.
* <p />
* 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 df001b2030..4cd716f4f4 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 bf09e7a88d..03cece3160 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 6769e85b23..7bf9fc29ab 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 e168e03839..d17f5664ab 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;
* }
* </pre>
*/
-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 61604f6242..b6a54cb870 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<String, String> params = parseParams(wwwAuthenticate);
+ Map<String, String> 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<String, String> parseParams(String wwwAuthenticate)
+ private Map<String, String> parseParameters(String wwwAuthenticate)
{
Map<String, String> result = new HashMap<>();
List<String> 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 3fee5eb749..eacbb09168 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/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
index 916efe9acb..644326aae5 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
@@ -30,10 +30,12 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -72,16 +74,18 @@ public class InputStreamResponseListener extends Response.Listener.Empty
{
private static final Logger LOG = Log.getLogger(InputStreamResponseListener.class);
private static final byte[] EOF = new byte[0];
- private static final byte[] CLOSE = new byte[0];
+ private static final byte[] CLOSED = new byte[0];
private static final byte[] FAILURE = new byte[0];
private final BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();
private final AtomicLong length = new AtomicLong();
private final CountDownLatch responseLatch = new CountDownLatch(1);
private final CountDownLatch resultLatch = new CountDownLatch(1);
+ private final AtomicReference<InputStream> stream = new AtomicReference<>();
private final long maxBufferSize;
private Response response;
private Result result;
private volatile Throwable failure;
+ private volatile boolean closed;
public InputStreamResponseListener()
{
@@ -103,6 +107,10 @@ public class InputStreamResponseListener extends Response.Listener.Empty
@Override
public void onContent(Response response, ByteBuffer content)
{
+ // Avoid buffering if the input stream is early closed.
+ if (closed)
+ return;
+
int remaining = content.remaining();
byte[] bytes = new byte[remaining];
content.get(bytes);
@@ -113,6 +121,7 @@ public class InputStreamResponseListener extends Response.Listener.Empty
while (newLength >= maxBufferSize)
{
LOG.debug("Queued bytes limit {}/{} exceeded, waiting", newLength, maxBufferSize);
+ // Block to avoid infinite buffering
if (!await())
break;
newLength = length.get();
@@ -123,10 +132,12 @@ public class InputStreamResponseListener extends Response.Listener.Empty
@Override
public void onFailure(Response response, Throwable failure)
{
- this.failure = failure;
LOG.debug("Queuing failure {} {}", FAILURE, failure);
queue.offer(FAILURE);
responseLatch.countDown();
+ resultLatch.countDown();
+ this.failure = failure;
+ signal();
}
@Override
@@ -143,15 +154,17 @@ public class InputStreamResponseListener extends Response.Listener.Empty
resultLatch.countDown();
}
- private boolean await()
+ protected boolean await()
{
try
{
synchronized (this)
{
- wait();
+ if (length.get() >= maxBufferSize && failure == null && !closed)
+ wait();
+ // Re-read the values as they may have changed while waiting.
+ return failure == null && !closed;
}
- return true;
}
catch (InterruptedException x)
{
@@ -159,7 +172,7 @@ public class InputStreamResponseListener extends Response.Listener.Empty
}
}
- private void signal()
+ protected void signal()
{
synchronized (this)
{
@@ -167,6 +180,19 @@ public class InputStreamResponseListener extends Response.Listener.Empty
}
}
+ /**
+ * Waits for the given timeout for the response to be available, then returns it.
+ * <p />
+ * The wait ends as soon as all the HTTP headers have been received, without waiting for the content.
+ * To wait for the whole content, see {@link #await(long, TimeUnit)}.
+ *
+ * @param timeout the time to wait
+ * @param unit the timeout unit
+ * @return the response
+ * @throws InterruptedException if the thread is interrupted
+ * @throws TimeoutException if the timeout expires
+ * @throws ExecutionException if a failure happened
+ */
public Response get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException, ExecutionException
{
boolean expired = !responseLatch.await(timeout, unit);
@@ -177,6 +203,18 @@ public class InputStreamResponseListener extends Response.Listener.Empty
return response;
}
+ /**
+ * Waits for the given timeout for the whole request/response cycle to be finished,
+ * then returns the corresponding result.
+ * <p />
+ *
+ * @param timeout the time to wait
+ * @param unit the timeout unit
+ * @return the result
+ * @throws InterruptedException if the thread is interrupted
+ * @throws TimeoutException if the timeout expires
+ * @see #get(long, TimeUnit)
+ */
public Result await(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
boolean expired = !resultLatch.await(timeout, unit);
@@ -185,9 +223,19 @@ public class InputStreamResponseListener extends Response.Listener.Empty
return result;
}
+ /**
+ * Returns an {@link InputStream} providing the response content bytes.
+ * <p />
+ * The method may be invoked only once; subsequent invocations will return a closed {@link InputStream}.
+ *
+ * @return an input stream providing the response content
+ */
public InputStream getInputStream()
{
- return new Input();
+ InputStream result = new Input();
+ if (stream.compareAndSet(null, result))
+ return result;
+ return IO.getClosedStream();
}
private class Input extends InputStream
@@ -211,7 +259,7 @@ public class InputStreamResponseListener extends Response.Listener.Empty
{
throw failure();
}
- else if (bytes == CLOSE)
+ else if (bytes == CLOSED)
{
if (index < 0)
return -1;
@@ -219,17 +267,20 @@ public class InputStreamResponseListener extends Response.Listener.Empty
}
else if (bytes != null)
{
- if (index < bytes.length)
- return bytes[index++] & 0xFF;
- length.addAndGet(-index);
- bytes = null;
- index = 0;
+ int result = bytes[index] & 0xFF;
+ if (++index == bytes.length)
+ {
+ length.addAndGet(-index);
+ bytes = null;
+ index = 0;
+ signal();
+ }
+ return result;
}
else
{
bytes = take();
LOG.debug("Dequeued {}/{} bytes", bytes, bytes.length);
- signal();
}
}
}
@@ -257,8 +308,10 @@ public class InputStreamResponseListener extends Response.Listener.Empty
@Override
public void close() throws IOException
{
- LOG.debug("Queuing close {}{}", CLOSE, "");
- queue.offer(CLOSE);
+ LOG.debug("Queuing close {}{}", CLOSED, "");
+ queue.offer(CLOSED);
+ closed = true;
+ signal();
super.close();
}
}
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 5fc20d1971..42e3b50516 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/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
index 7fe669c423..d97093c3b7 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
@@ -62,6 +62,7 @@ public class HostnameVerificationTest
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd");
}
+ sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
if (server == null)
server = new Server();
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 0000000000..c645f1cdb3
--- /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 f4dac19a63..218f0e090a 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,11 +32,13 @@ 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;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -45,7 +47,9 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.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;
@@ -58,7 +62,6 @@ import org.junit.Assert;
import org.junit.Test;
import static java.nio.file.StandardOpenOption.CREATE;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -181,10 +184,10 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
{
int read = input.read();
assertTrue(read >= 0);
- assertEquals(b & 0xFF, read);
+ Assert.assertEquals(b & 0xFF, read);
}
- assertEquals(-1, input.read());
+ Assert.assertEquals(-1, input.read());
Result result = listener.await(5, TimeUnit.SECONDS);
Assert.assertNotNull(result);
@@ -248,6 +251,232 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
}
@Test(expected = AsynchronousCloseException.class)
+ public void testInputStreamResponseListenerClosedBeforeReading() 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());
+ }
+ });
+
+ InputStreamResponseListener listener = new InputStreamResponseListener();
+ InputStream stream = listener.getInputStream();
+ // Close the stream immediately
+ stream.close();
+
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .content(new BytesContentProvider(new byte[]{0, 1, 2, 3}))
+ .send(listener);
+ Response response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(200, response.getStatus());
+
+ stream.read(); // Throws
+ }
+
+ @Test
+ public void testInputStreamResponseListenerClosedWhileWaiting() throws Exception
+ {
+ final byte[] chunk1 = new byte[]{0, 1};
+ final byte[] chunk2 = new byte[]{2, 3};
+ final CountDownLatch closeLatch = new CountDownLatch(1);
+ 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);
+ response.setContentLength(chunk1.length + chunk2.length);
+ ServletOutputStream output = response.getOutputStream();
+ output.write(chunk1);
+ output.flush();
+ try
+ {
+ closeLatch.await(5, TimeUnit.SECONDS);
+ output.write(chunk2);
+ output.flush();
+ }
+ catch (InterruptedException x)
+ {
+ throw new InterruptedIOException();
+ }
+ }
+ });
+
+ final CountDownLatch waitLatch = new CountDownLatch(1);
+ final CountDownLatch waitedLatch = new CountDownLatch(1);
+ InputStreamResponseListener listener = new InputStreamResponseListener(1)
+ {
+ @Override
+ protected boolean await()
+ {
+ waitLatch.countDown();
+ boolean result = super.await();
+ waitedLatch.countDown();
+ return result;
+ }
+ };
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .send(listener);
+ Response response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(200, response.getStatus());
+
+ InputStream stream = listener.getInputStream();
+ // Wait until we block
+ Assert.assertTrue(waitLatch.await(5, TimeUnit.SECONDS));
+ // Close the stream
+ stream.close();
+ closeLatch.countDown();
+
+ // Be sure we're not stuck waiting
+ Assert.assertTrue(waitedLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testInputStreamResponseListenerFailedWhileWaiting() throws Exception
+ {
+ final byte[] chunk1 = new byte[]{0, 1};
+ final byte[] chunk2 = new byte[]{2, 3};
+ final CountDownLatch closeLatch = new CountDownLatch(1);
+ 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);
+ response.setContentLength(chunk1.length + chunk2.length);
+ ServletOutputStream output = response.getOutputStream();
+ output.write(chunk1);
+ output.flush();
+ try
+ {
+ closeLatch.await(5, TimeUnit.SECONDS);
+ output.write(chunk2);
+ output.flush();
+ }
+ catch (InterruptedException x)
+ {
+ throw new InterruptedIOException();
+ }
+ }
+ });
+
+ final CountDownLatch waitLatch = new CountDownLatch(1);
+ final CountDownLatch waitedLatch = new CountDownLatch(1);
+ InputStreamResponseListener listener = new InputStreamResponseListener(1)
+ {
+ @Override
+ protected boolean await()
+ {
+ waitLatch.countDown();
+ boolean result = super.await();
+ waitedLatch.countDown();
+ return result;
+ }
+ };
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .send(listener);
+ Response response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(200, response.getStatus());
+
+ // Wait until we block
+ Assert.assertTrue(waitLatch.await(5, TimeUnit.SECONDS));
+ // Fail the response
+ response.abort(new Exception());
+ closeLatch.countDown();
+
+ // Be sure we're not stuck waiting
+ Assert.assertTrue(waitedLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testInputStreamResponseListenerConsumingBeforeWaiting() throws Exception
+ {
+ final byte[] data = new byte[]{0, 1};
+ 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);
+ response.setContentLength(data.length);
+ ServletOutputStream output = response.getOutputStream();
+ output.write(data);
+ output.flush();
+ }
+ });
+
+ final AtomicReference<Throwable> failure = new AtomicReference<>();
+ InputStreamResponseListener listener = new InputStreamResponseListener(1)
+ {
+ @Override
+ protected boolean await()
+ {
+ // Consume everything just before waiting
+ InputStream stream = getInputStream();
+ consume(stream, data);
+ return super.await();
+ }
+
+ private void consume(InputStream stream, byte[] data)
+ {
+ try
+ {
+ for (byte datum : data)
+ Assert.assertEquals(datum, stream.read());
+ }
+ catch (IOException x)
+ {
+ failure.compareAndSet(null, x);
+ }
+ }
+ };
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .send(listener);
+ Result result = listener.await(5, TimeUnit.SECONDS);
+ Assert.assertEquals(200, result.getResponse().getStatus());
+ 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
{
final byte[] data = new byte[128 * 1024];
@@ -289,7 +518,7 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
latch.countDown();
- input.read();
+ input.read(); // Throws
}
@Test(expected = AsynchronousCloseException.class)
@@ -331,14 +560,14 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
InputStream input = listener.getInputStream();
Assert.assertNotNull(input);
- for (byte b : data1)
- input.read();
+ for (byte datum1 : data1)
+ Assert.assertEquals(datum1, input.read());
input.close();
latch.countDown();
- input.read(); // throws
+ input.read(); // Throws
}
@Test
@@ -367,8 +596,8 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest
InputStream input = listener.getInputStream();
Assert.assertNotNull(input);
- for (byte b : data)
- input.read();
+ for (byte datum : data)
+ Assert.assertEquals(datum, input.read());
// Read EOF
Assert.assertEquals(-1, input.read());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 225078050b..f01693dbf5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -255,6 +255,38 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
@Test
+ public void test_PUT_WithParameters() throws Exception
+ {
+ final String paramName = "a";
+ final String paramValue = "\u20AC";
+ 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 value = request.getParameter(paramName);
+ if (paramValue.equals(value))
+ {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("text/plain");
+ response.getOutputStream().print(value);
+ }
+ }
+ });
+
+ URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
+ ContentResponse response = client.newRequest(uri)
+ .method(HttpMethod.PUT)
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+ }
+
+ @Test
public void test_POST_WithParameters_WithContent() throws Exception
{
final byte[] content = {0, 1, 2, 3};
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
new file mode 100644
index 0000000000..67c99310cc
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
@@ -0,0 +1,369 @@
+//
+// ========================================================================
+// 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.ssl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+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 javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SslBytesClientTest extends SslBytesTest
+{
+ private ExecutorService threadPool;
+ private HttpClient client;
+ private SslContextFactory sslContextFactory;
+ private SSLServerSocket acceptor;
+ private SimpleProxy proxy;
+
+ @Before
+ public void init() throws Exception
+ {
+ threadPool = Executors.newCachedThreadPool();
+
+ client = new HttpClient(new SslContextFactory(true));
+ client.setMaxConnectionsPerDestination(1);
+ File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
+ sslContextFactory = client.getSslContextFactory();
+ sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
+ sslContextFactory.setKeyStorePassword("storepwd");
+ client.start();
+
+ SSLContext sslContext = sslContextFactory.getSslContext();
+ acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(43191);
+
+ int serverPort = acceptor.getLocalPort();
+
+ proxy = new SimpleProxy(threadPool, "localhost", serverPort);
+ proxy.start();
+ logger.info(":{} <==> :{}", proxy.getPort(), serverPort);
+ }
+
+ @After
+ public void destroy() throws Exception
+ {
+ if (acceptor != null)
+ acceptor.close();
+ if (proxy != null)
+ proxy.stop();
+ if (client != null)
+ client.stop();
+ if (threadPool != null)
+ threadPool.shutdownNow();
+ }
+
+ @Test
+ public void testHandshake() throws Exception
+ {
+ Request request = client.newRequest("localhost", proxy.getPort());
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+ Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+ final SSLSocket server = (SSLSocket)acceptor.accept();
+ server.setUseClientMode(false);
+
+ Future<Object> handshake = threadPool.submit(new Callable<Object>()
+ {
+ public Object call() throws Exception
+ {
+ server.startHandshake();
+ return null;
+ }
+ });
+
+ // Client Hello
+ TLSRecord record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ // Server Hello + Certificate + Server Done
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ // Client Key Exchange
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ // Change Cipher Spec
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+ proxy.flushToServer(record);
+
+ // Client Done
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ // Change Cipher Spec
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+ proxy.flushToClient(record);
+
+ // Server Done
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+ SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+ // Read request
+ BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), "UTF-8"));
+ String line = reader.readLine();
+ Assert.assertTrue(line.startsWith("GET"));
+ while (line.length() > 0)
+ line = reader.readLine();
+
+ // Write response
+ OutputStream output = server.getOutputStream();
+ output.write(("HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 0\r\n" +
+ "\r\n").getBytes("UTF-8"));
+ output.flush();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ server.close();
+ }
+
+ @Test
+ public void testServerRenegotiation() throws Exception
+ {
+ Request request = client.newRequest("localhost", proxy.getPort());
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+ Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+ final SSLSocket server = (SSLSocket)acceptor.accept();
+ server.setUseClientMode(false);
+
+ Future<Object> handshake = threadPool.submit(new Callable<Object>()
+ {
+ public Object call() throws Exception
+ {
+ server.startHandshake();
+ return null;
+ }
+ });
+
+ SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+ Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+ // Read request
+ InputStream serverInput = server.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
+ String line = reader.readLine();
+ Assert.assertTrue(line.startsWith("GET"));
+ while (line.length() > 0)
+ line = reader.readLine();
+
+ OutputStream serverOutput = server.getOutputStream();
+ byte[] data1 = new byte[1024];
+ Arrays.fill(data1, (byte)'X');
+ String content1 = new String(data1, "UTF-8");
+ byte[] data2 = new byte[1024];
+ Arrays.fill(data2, (byte)'Y');
+ final String content2 = new String(data2, "UTF-8");
+ // Write first part of the response
+ serverOutput.write(("HTTP/1.1 200 OK\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+ "\r\n" +
+ content1).getBytes("UTF-8"));
+ serverOutput.flush();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ // Renegotiate
+ Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
+ {
+ public Object call() throws Exception
+ {
+ server.startHandshake();
+ return null;
+ }
+ });
+
+ // Renegotiation Handshake
+ TLSRecord record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ // Renegotiation Handshake
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ // Trigger a read to have the server write the final renegotiation steps
+ server.setSoTimeout(100);
+ try
+ {
+ serverInput.read();
+ Assert.fail();
+ }
+ catch (SocketTimeoutException x)
+ {
+ // Expected
+ }
+
+ // Renegotiation Handshake
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ // Renegotiation Change Cipher
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+ proxy.flushToClient(record);
+
+ // Renegotiation Handshake
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ // Renegotiation Change Cipher
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+ proxy.flushToServer(record);
+
+ // Renegotiation Handshake
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
+
+ // Complete the response
+ automaticProxyFlow = proxy.startAutomaticFlow();
+ serverOutput.write(data2);
+ serverOutput.flush();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ Assert.assertEquals(data1.length + data2.length, response.getContent().length);
+
+ server.close();
+ }
+
+ @Test
+ public void testServerRenegotiationWhenRenegotiationIsForbidden() throws Exception
+ {
+ sslContextFactory.setRenegotiationAllowed(false);
+
+ Request request = client.newRequest("localhost", proxy.getPort());
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+ Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+ final SSLSocket server = (SSLSocket)acceptor.accept();
+ server.setUseClientMode(false);
+
+ Future<Object> handshake = threadPool.submit(new Callable<Object>()
+ {
+ public Object call() throws Exception
+ {
+ server.startHandshake();
+ return null;
+ }
+ });
+
+ SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+ Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+ // Read request
+ InputStream serverInput = server.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
+ String line = reader.readLine();
+ Assert.assertTrue(line.startsWith("GET"));
+ while (line.length() > 0)
+ line = reader.readLine();
+
+ OutputStream serverOutput = server.getOutputStream();
+ byte[] data1 = new byte[1024];
+ Arrays.fill(data1, (byte)'X');
+ String content1 = new String(data1, "UTF-8");
+ byte[] data2 = new byte[1024];
+ Arrays.fill(data2, (byte)'Y');
+ final String content2 = new String(data2, "UTF-8");
+ // Write first part of the response
+ serverOutput.write(("HTTP/1.1 200 OK\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+ "\r\n" +
+ content1).getBytes("UTF-8"));
+ serverOutput.flush();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ // Renegotiate
+ threadPool.submit(new Callable<Object>()
+ {
+ public Object call() throws Exception
+ {
+ server.startHandshake();
+ return null;
+ }
+ });
+
+ // Renegotiation Handshake
+ TLSRecord record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToClient(record);
+
+ record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+ proxy.flushToServer(record);
+
+ record = proxy.readFromClient();
+ Assert.assertNull(record);
+ proxy.flushToServer(record);
+
+ server.close();
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index 6592a3aa68..1faca02d88 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.server.ssl;
+package org.eclipse.jetty.client.ssl;
import java.io.BufferedReader;
import java.io.EOFException;
@@ -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;
@@ -85,7 +84,7 @@ public class SslBytesServerTest extends SslBytesTest
private final int idleTimeout = 2000;
private ExecutorService threadPool;
private Server server;
- private int serverPort;
+ private SslContextFactory sslContextFactory;
private SSLContext sslContext;
private SimpleProxy proxy;
@@ -95,11 +94,10 @@ public class SslBytesServerTest extends SslBytesTest
threadPool = Executors.newCachedThreadPool();
server = new Server();
- File keyStore = MavenTestingUtils.getTestResourceFile("keystore");
- SslContextFactory sslContextFactory = new SslContextFactory();
+ File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
+ sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
- sslContextFactory.setKeyManagerPassword("keypwd");
HttpConnectionFactory httpFactory = new HttpConnectionFactory()
{
@@ -205,7 +203,7 @@ public class SslBytesServerTest extends SslBytesTest
}
});
server.start();
- serverPort = connector.getLocalPort();
+ int serverPort = connector.getLocalPort();
sslContext = sslContextFactory.getSslContext();
@@ -866,7 +864,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<Object> request = threadPool.submit(new Callable<Object>()
+ {
+ @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);
@@ -1224,6 +1277,98 @@ public class SslBytesServerTest extends SslBytesTest
}
@Test
+ public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception
+ {
+ assumeJavaVersionSupportsTLSRenegotiations();
+
+ sslContextFactory.setRenegotiationAllowed(false);
+
+ final SSLSocket client = newClient();
+ final OutputStream clientOutput = client.getOutputStream();
+
+ SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+ client.startHandshake();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ byte[] data1 = new byte[1024];
+ Arrays.fill(data1, (byte)'X');
+ String content1 = new String(data1, "UTF-8");
+ byte[] data2 = new byte[1024];
+ Arrays.fill(data2, (byte)'Y');
+ final String content2 = new String(data2, "UTF-8");
+
+ // Write only part of the body
+ automaticProxyFlow = proxy.startAutomaticFlow();
+ clientOutput.write(("" +
+ "POST / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+ "\r\n" +
+ content1).getBytes("UTF-8"));
+ clientOutput.flush();
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+ // Renegotiate
+ threadPool.submit(new Callable<Object>()
+ {
+ @Override
+ public Object call() throws Exception
+ {
+ client.startHandshake();
+ return null;
+ }
+ });
+
+ // Renegotiation Handshake
+ TLSRecord record = proxy.readFromClient();
+ Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+ proxy.flushToServer(record);
+
+ // Renegotiation now allowed, server has closed
+ record = proxy.readFromServer();
+ Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+ proxy.flushToClient(record);
+
+ record = proxy.readFromServer();
+ Assert.assertNull(record);
+
+ // Write the rest of the request
+ threadPool.submit(new Callable<Object>()
+ {
+ @Override
+ public Object call() throws Exception
+ {
+ clientOutput.write(content2.getBytes("UTF-8"));
+ clientOutput.flush();
+ return null;
+ }
+ });
+
+ // Trying to write more application data results in an exception since the server closed
+ record = proxy.readFromClient();
+ proxy.flushToServer(record);
+ try
+ {
+ record = proxy.readFromClient();
+ Assert.assertNotNull(record);
+ proxy.flushToServer(record);
+ Assert.fail();
+ }
+ catch (IOException expected)
+ {
+ }
+
+ // Check that we did not spin
+ TimeUnit.MILLISECONDS.sleep(500);
+ Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+ Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+ Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+ client.close();
+ }
+
+ @Test
public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception
{
assumeJavaVersionSupportsTLSRenegotiations();
@@ -1634,9 +1779,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-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
index a3b4952fec..c7fe7447dd 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslBytesTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.server.ssl;
+package org.eclipse.jetty.client.ssl;
import java.io.EOFException;
import java.io.IOException;
@@ -115,8 +115,8 @@ public abstract class SslBytesTest
public void start() throws Exception
{
-// serverSocket = new ServerSocket(5871);
- serverSocket = new ServerSocket(0);
+ serverSocket = new ServerSocket(47009);
+// serverSocket = new ServerSocket(0);
Thread acceptor = new Thread(this);
acceptor.start();
server = new Socket(serverHost, serverPort);
diff --git a/jetty-client/src/test/resources/keystore b/jetty-client/src/test/resources/keystore
deleted file mode 100644
index 3a15d1b603..0000000000
--- a/jetty-client/src/test/resources/keystore
+++ /dev/null
Binary files differ
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 87b923a0b2..3f9ba0a3a3 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -10,7 +10,7 @@
<packaging>pom</packaging>
<properties>
<assembly-directory>target/distribution</assembly-directory>
- <jetty-setuid-version>1.0.0</jetty-setuid-version>
+ <jetty-setuid-version>1.0.1</jetty-setuid-version>
</properties>
<build>
<plugins>
@@ -219,7 +219,25 @@
<outputDirectory>${assembly-directory}/lib/websocket</outputDirectory>
</configuration>
</execution>
-
+ <execution>
+ <id>copy-lib-monitor-deps</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-monitor</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${assembly-directory}/lib/monitor</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
<execution>
<id>copy-orbit-servlet-api-deps</id>
<phase>generate-resources</phase>
@@ -423,6 +441,11 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-monitor</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-start</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index 62a9404493..cc3f771aa1 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -52,7 +52,7 @@
#
# <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg>
#
-# 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
@@ -110,6 +112,24 @@ running()
kill -0 "$PID" 2>/dev/null
}
+started()
+{
+ # wait for 60s to see "STARTED" in PID file, needs jetty-started.xml as argument
+ for T in 1 2 3 4 5 6 7 9 10 11 12 13 14 15
+ do
+ sleep 4
+ [ -z "$(grep STARTED $1)" ] || return 0
+ [ -z "$(grep STOPPED $1)" ] || return 1
+ [ -z "$(grep FAILED $1)" ] || return 1
+ local PID=$(cat "$2" 2>/dev/null) || return 1
+ kill -0 "$PID" 2>/dev/null || return 1
+ echo -n ". "
+ done
+
+ return 1;
+}
+
+
readConfig()
{
(( DEBUG )) && echo "Reading $1.."
@@ -137,7 +157,13 @@ shift
##################################################
# Read any configuration files
##################################################
-for CONFIG in /etc/default/jetty{,9} $HOME/.jettyrc; do
+ETC=/etc
+if [ $UID != 0 ]
+then
+ ETC=$HOME/etc
+fi
+
+for CONFIG in $ETC/default/jetty{,9} $HOME/.jettyrc; do
if [ -f "$CONFIG" ] ; then
readConfig "$CONFIG"
fi
@@ -262,9 +288,9 @@ fi
##################################################
if [ -z "$JETTY_CONF" ]
then
- if [ -f /etc/jetty.conf ]
+ if [ -f $ETC/jetty.conf ]
then
- JETTY_CONF=/etc/jetty.conf
+ JETTY_CONF=$ETC/jetty.conf
elif [ -f "$JETTY_HOME/etc/jetty.conf" ]
then
JETTY_CONF=$JETTY_HOME/etc/jetty.conf
@@ -318,6 +344,9 @@ if [ -z "$JETTY_PID" ]
then
JETTY_PID="$JETTY_RUN/jetty.pid"
fi
+JETTY_STATE=$(dirname $JETTY_PID)/jetty.state
+JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
+rm -f $JETTY_STATE
##################################################
# Setup JAVA if unset
@@ -407,23 +436,15 @@ case "$ACTION" in
exit
fi
- if type start-stop-daemon > /dev/null 2>&1
+ if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1
then
unset CH_USER
if [ -n "$JETTY_USER" ]
then
CH_USER="-c$JETTY_USER"
fi
- if start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_HOME" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" --daemon
- then
- sleep 1
- if running "$JETTY_PID"
- then
- echo "OK"
- else
- echo "FAILED"
- fi
- fi
+
+ start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_HOME" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" --daemon
else
@@ -454,14 +475,25 @@ case "$ACTION" in
echo $! > "$JETTY_PID"
fi
- echo "STARTED Jetty `date`"
+ fi
+
+ if expr "${CONFIGS[*]}" : '.*etc/jetty-started.xml.*' >/dev/null
+ then
+ if started "$JETTY_STATE" "$JETTY_PID"
+ then
+ echo "OK `date`"
+ else
+ echo "FAILED `date`"
+ fi
+ else
+ echo "ok `date`"
fi
;;
stop)
echo -n "Stopping Jetty: "
- if type start-stop-daemon > /dev/null 2>&1; then
+ if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1; then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP
TIMEOUT=30
@@ -537,7 +569,7 @@ case "$ACTION" in
;;
- check)
+ check|status)
echo "Checking arguments to Jetty: "
echo "JETTY_HOME = $JETTY_HOME"
echo "JETTY_CONF = $JETTY_CONF"
diff --git a/jetty-distribution/src/main/resources/etc/jetty-started.xml b/jetty-distribution/src/main/resources/etc/jetty-started.xml
new file mode 100644
index 0000000000..9207b1c9b4
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jetty-started.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Start FileNoticeLifeCycleListener -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+ <Call name="addLifeCycleListener">
+ <Arg>
+ <New class="org.eclipse.jetty.util.component.FileNoticeLifeCycleListener">
+ <Arg><SystemProperty name="jetty.state" default="./jetty.state"/></Arg>
+ </New>
+ </Arg>
+ </Call>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/jetty.conf b/jetty-distribution/src/main/resources/etc/jetty.conf
index b79f3169fb..2e2e1aa845 100644
--- a/jetty-distribution/src/main/resources/etc/jetty.conf
+++ b/jetty-distribution/src/main/resources/etc/jetty.conf
@@ -6,8 +6,7 @@
# created by that script.
#
# Each line in this file becomes an arguement to start.jar
-# unless this file contains an --ini option, then these
-# arguments will be in addition to those found in the
-# start.ini file
+# in addition to those found in the start.ini file
# =======================================================
---pre=etc/jetty-logging.xml
+etc/jetty-logging.xml
+etc/jetty-started.xml
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index 641b19c901..382010d19b 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
#===========================================================
@@ -84,27 +80,27 @@
#===========================================================
#===========================================================
-# Enable SetUID
-# To enable setuid you must have the jetty-setuid.xml as the
-# first xml file to be processed.
-# The default user and group is 'jetty' and if you are
-# starting as root you must change the run privledged to true
-#-----------------------------------------------------------
-# OPTIONS=setuid
-# etc/jetty-setuid.xml
-#===========================================================
-
-#===========================================================
# Default Server Options
# Use the core server jars with websocket on the classpath
# 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/
+#===========================================================
+
+#===========================================================
+# Enable SetUID
+# The default user and group is 'jetty' and if you are
+# starting as root you must change the run privledged to true
+#-----------------------------------------------------------
+# OPTIONS=setuid
+# etc/jetty-setuid.xml
+# jetty.startServerAsPrivileged=false
+# jetty.username=jetty
+# jetty.groupname=jetty
+# jetty.umask=002
#===========================================================
#===========================================================
@@ -121,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
#===========================================================
@@ -144,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 iwhich must be separately downloaded:
+# SPDY requires the NPN jar which must be separately downloaded:
#
-# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.2.v20130305/npn-boot-1.1.2.v20130305.jar
+# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.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
#===========================================================
@@ -202,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/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
index daead029f9..4bc0705350 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
@@ -112,16 +112,14 @@ public interface HttpContent
@Override
public ByteBuffer getIndirectBuffer()
{
- try
- {
- if (_resource.length()<=0 || _maxBuffer<_resource.length())
- return null;
- int length=(int)_resource.length();
- byte[] array = new byte[length];
-
- int offset=0;
- InputStream in=_resource.getInputStream();
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ int length=(int)_resource.length();
+ byte[] array = new byte[length];
+ int offset=0;
+ try (InputStream in=_resource.getInputStream())
+ {
do
{
int filled=in.read(array,offset,length);
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 f59548589d..67c95111e1 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<HttpField>
private static final Float __one = new Float("1.0");
private static final Float __zero = new Float("0.0");
- private static final StringMap<Float> __qualities = new StringMap<>();
+ private static final Trie<Float> __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 d8e25434d3..0385c8ed66 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<O> extends HashMap<String,O>
}
/* --------------------------------------------------------------- */
- final StringMap<MappedEntry<O>> _prefixMap=new StringMap<>();
- final StringMap<MappedEntry<O>> _suffixMap=new StringMap<>();
- final StringMap<MappedEntry<O>> _exactMap=new StringMap<>();
+ Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
- List _defaultSingletonList=null;
+ List<MappedEntry<O>> _defaultSingletonList=null;
MappedEntry<O> _prefixDefault=null;
MappedEntry<O> _default=null;
- final Set _entrySet;
boolean _nodefault=false;
/* --------------------------------------------------------------- */
@@ -111,7 +110,6 @@ public class PathMap<O> extends HashMap<String,O>
{
super(capacity);
_nodefault=noDefault;
- _entrySet=entrySet();
}
/* --------------------------------------------------------------- */
@@ -120,7 +118,6 @@ public class PathMap<O> extends HashMap<String,O>
public PathMap(Map<String, ? extends O> m)
{
putAll(m);
- _entrySet=entrySet();
}
/* --------------------------------------------------------------- */
@@ -163,12 +160,15 @@ public class PathMap<O> extends HashMap<String,O>
{
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<MappedEntry<O>>)_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<MappedEntry<O>>)_suffixMap,1.5);
+ }
else if (spec.equals(URIUtil.SLASH))
{
if (_nodefault)
@@ -176,8 +176,7 @@ public class PathMap<O> extends HashMap<String,O>
else
{
_default=entry;
- _defaultSingletonList=
- Collections.singletonList(_default);
+ _defaultSingletonList=Collections.singletonList(_default);
}
}
else
@@ -228,17 +227,22 @@ public class PathMap<O> extends HashMap<String,O>
}
// 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<PathMap.MappedEntry<O>> 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<O> extends HashMap<String,O>
// Extension search
i=0;
+ final Trie<PathMap.MappedEntry<O>> 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<O> extends HashMap<String,O>
*/
public Object getLazyMatches(String path)
{
- MappedEntry entry;
+ MappedEntry<O> 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<PathMap.MappedEntry<O>> 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<O> extends HashMap<String,O>
// Extension search
i=0;
+ final Trie<PathMap.MappedEntry<O>> 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<O> extends HashMap<String,O>
* @param path Path to match
* @return List of Map.Entry instances key=pathSpec
*/
- public List getMatches(String path)
+ public List<Map.Entry<String,O>> getMatches(String path)
{
return LazyList.getList(getLazyMatches(path));
}
@@ -333,7 +344,7 @@ public class PathMap<O> extends HashMap<String,O>
*/
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<O> extends HashMap<String,O>
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<O> extends HashMap<String,O>
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<O> extends HashMap<String,O>
* @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<O> extends HashMap<String,O>
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 cda9f11e63..36e9b2e158 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/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
index 7549903af5..4ec1d84459 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -142,14 +142,11 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
@Override
protected void onIdleExpired(TimeoutException timeout)
{
- if (isOutputShutdown() || _fillInterest.isInterested() || _writeFlusher.isInProgress())
- {
- boolean output_shutdown=isOutputShutdown();
- _fillInterest.onFail(timeout);
- _writeFlusher.onFail(timeout);
- if (output_shutdown)
- close();
- }
+ boolean output_shutdown=isOutputShutdown();
+ _fillInterest.onFail(timeout);
+ _writeFlusher.onFail(timeout);
+ if (output_shutdown)
+ close();
}
@Override
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java
index bb2870f63c..dd81b4656a 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java
@@ -26,16 +26,13 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
-
-
-/* ------------------------------------------------------------ */
-/** An Abstract implementation of an Idle Timeout.
- *
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
* This implementation is optimised that timeout operations are not cancelled on
* every operation. Rather timeout are allowed to expire and a check is then made
* to see when the last operation took place. If the idle timeout has not expired,
* the timeout is rescheduled for the earliest possible time a timeout could occur.
- *
*/
public abstract class IdleTimeout
{
@@ -43,15 +40,15 @@ public abstract class IdleTimeout
private final Scheduler _scheduler;
private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
private volatile long _idleTimeout;
- private volatile long _idleTimestamp=System.currentTimeMillis();
+ private volatile long _idleTimestamp = System.currentTimeMillis();
private final Runnable _idleTask = new Runnable()
{
@Override
public void run()
{
- long idleLeft=checkIdleTimeout();
- if (idleLeft>=0)
+ long idleLeft = checkIdleTimeout();
+ if (idleLeft >= 0)
scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
}
};
@@ -61,7 +58,7 @@ public abstract class IdleTimeout
*/
public IdleTimeout(Scheduler scheduler)
{
- _scheduler=scheduler;
+ _scheduler = scheduler;
}
public long getIdleTimestamp()
@@ -76,14 +73,14 @@ public abstract class IdleTimeout
public void setIdleTimeout(long idleTimeout)
{
- long old=_idleTimeout;
+ long old = _idleTimeout;
_idleTimeout = idleTimeout;
// Do we have an old timeout
- if (old>0)
+ if (old > 0)
{
// if the old was less than or equal to the new timeout, then nothing more to do
- if (old<=idleTimeout)
+ if (old <= idleTimeout)
return;
// old timeout is too long, so cancel it.
@@ -93,22 +90,22 @@ public abstract class IdleTimeout
}
// If we have a new timeout, then check and reschedule
- if (idleTimeout>0 && isOpen())
+ if (idleTimeout > 0 && isOpen())
_idleTask.run();
-
}
- /** This method should be called when non-idle activity has taken place.
+ /**
+ * This method should be called when non-idle activity has taken place.
*/
public void notIdle()
{
- _idleTimestamp=System.currentTimeMillis();
+ _idleTimestamp = System.currentTimeMillis();
}
private void scheduleIdleTimeout(long delay)
{
Scheduler.Task newTimeout = null;
- if (isOpen() && delay > 0 && _scheduler!=null)
+ if (isOpen() && delay > 0 && _scheduler != null)
newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
if (oldTimeout != null)
@@ -117,10 +114,10 @@ public abstract class IdleTimeout
public void onOpen()
{
- if (_idleTimeout>0)
+ if (_idleTimeout > 0)
_idleTask.run();
}
-
+
public void onClose()
{
Scheduler.Task oldTimeout = _timeout.getAndSet(null);
@@ -162,22 +159,23 @@ public abstract class IdleTimeout
}
}
- return idleLeft>=0?idleLeft:0;
+ return idleLeft >= 0 ? idleLeft : 0;
}
return -1;
}
- /* ------------------------------------------------------------ */
- /** This abstract method is called when the idle timeout has expired.
+ /**
+ * This abstract method is called when the idle timeout has expired.
+ *
* @param timeout a TimeoutException
*/
- abstract protected void onIdleExpired(TimeoutException timeout);
-
+ protected abstract void onIdleExpired(TimeoutException timeout);
- /* ------------------------------------------------------------ */
- /** This abstract method should be called to check if idle timeouts
+ /**
+ * This abstract method should be called to check if idle timeouts
* should still be checked.
+ *
* @return True if the entity monitored should still be checked for idle timeouts
*/
- abstract protected boolean isOpen();
+ public abstract boolean isOpen();
}
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 41b031fb01..317c55ef6d 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)
@@ -166,6 +167,20 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
selector.submit(selector.new Accept(channel));
}
+ /**
+ * <p>Registers a channel to perform non-blocking read/write operations.</p>
+ * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+ * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
+ *
+ * @param channel the channel to register
+ * @param attachment An attachment to be passed via the selection key to the {@link SelectorManager#newConnection(SocketChannel, EndPoint, Object)} method.
+ */
+ public void accept(final SocketChannel channel, Object attachment)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Accept(channel, attachment));
+ }
+
@Override
protected void doStart() throws Exception
{
@@ -317,7 +332,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
*/
public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
{
- private final Queue<Runnable> _changes = new ConcurrentLinkedQueue<>();
+ private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
+
private final int _id;
private Selector _selector;
private volatile Thread _thread;
@@ -364,7 +380,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
@@ -683,10 +699,18 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
private class Accept implements Runnable
{
private final SocketChannel _channel;
+ private final Object _attachment;
public Accept(SocketChannel channel)
{
this._channel = channel;
+ this._attachment = null;
+ }
+
+ public Accept(SocketChannel channel, Object attachment)
+ {
+ this._channel = channel;
+ this._attachment = attachment;
}
@Override
@@ -694,7 +718,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
{
try
{
- SelectionKey key = _channel.register(_selector, 0, null);
+ SelectionKey key = _channel.register(_selector, 0, _attachment);
EndPoint endpoint = createEndPoint(_channel, key);
key.attach(endpoint);
}
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 86ddca5ce4..4fca18f3f1 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
@@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.concurrent.Executor;
-
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -67,8 +66,8 @@ import org.eclipse.jetty.util.log.Logger;
* encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
* <p>
* It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
- * {@link DecryptedEndPoint#write(Object, Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
- * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Object, Callback, ByteBuffer...)}
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
* methods. For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
* write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
* to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
@@ -100,6 +99,7 @@ public class SslConnection extends AbstractConnection
_decryptedEndPoint.getWriteFlusher().completeWrite();
}
};
+ private boolean _renegotiationAllowed;
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
{
@@ -114,7 +114,7 @@ public class SslConnection extends AbstractConnection
{
try
{
- ((SocketBased) endPoint).getSocket().setSoLinger(true,30000);
+ ((SocketBased)endPoint).getSocket().setSoLinger(true, 30000);
}
catch (SocketException e)
{
@@ -138,6 +138,16 @@ public class SslConnection extends AbstractConnection
return _decryptedEndPoint;
}
+ public boolean isRenegotiationAllowed()
+ {
+ return _renegotiationAllowed;
+ }
+
+ public void setRenegotiationAllowed(boolean renegotiationAllowed)
+ {
+ this._renegotiationAllowed = renegotiationAllowed;
+ }
+
@Override
public void onOpen()
{
@@ -162,25 +172,12 @@ public class SslConnection extends AbstractConnection
super.onClose();
}
-// @Override
-// public int getMessagesIn()
-// {
-// return _decryptedEndPoint.getConnection().getMessagesIn();
-// }
-//
-// @Override
-// public int getMessagesOut()
-// {
-// return _decryptedEndPoint.getConnection().getMessagesOut();
-// }
-
@Override
public void close()
{
getDecryptedEndPoint().getConnection().close();
}
- /* ------------------------------------------------------------ */
@Override
public void onFillable()
{
@@ -211,7 +208,6 @@ public class SslConnection extends AbstractConnection
LOG.debug("onFillable exit {}", getEndPoint());
}
- /* ------------------------------------------------------------ */
@Override
public void onFillInterestedFailed(Throwable cause)
{
@@ -220,19 +216,21 @@ public class SslConnection extends AbstractConnection
// the decrypted readInterest and/or writeFlusher so that they will attempt
// to do the fill and/or flush again and these calls will do the actually
// handle the cause.
+ _decryptedEndPoint.getFillInterest().onFail(cause);
+
+ boolean failFlusher = false;
synchronized(_decryptedEndPoint)
{
- _decryptedEndPoint.getFillInterest().onFail(cause);
-
if (_decryptedEndPoint._flushRequiresFillToProgress)
{
_decryptedEndPoint._flushRequiresFillToProgress = false;
- _decryptedEndPoint.getWriteFlusher().onFail(cause);
+ failFlusher = true;
}
}
+ if (failFlusher)
+ _decryptedEndPoint.getWriteFlusher().onFail(cause);
}
- /* ------------------------------------------------------------ */
@Override
public String toString()
{
@@ -250,12 +248,12 @@ public class SslConnection extends AbstractConnection
_decryptedEndPoint.getConnection());
}
- /* ------------------------------------------------------------ */
public class DecryptedEndPoint extends AbstractEndPoint
{
private boolean _fillRequiresFlushToProgress;
private boolean _flushRequiresFillToProgress;
private boolean _cannotAcceptMoreAppDataToFlush;
+ private boolean _handshaken;
private boolean _underFlown;
private final Callback _writeCallback = new Callback()
@@ -266,6 +264,7 @@ public class SslConnection extends AbstractConnection
// This means that a write of encrypted data has completed. Writes are done
// only if there is a pending writeflusher or a read needed to write
// data. In either case the appropriate callback is passed on.
+ boolean fillable = false;
synchronized (DecryptedEndPoint.this)
{
if (DEBUG)
@@ -278,11 +277,12 @@ public class SslConnection extends AbstractConnection
if (_fillRequiresFlushToProgress)
{
_fillRequiresFlushToProgress = false;
- getFillInterest().fillable();
+ fillable = true;
}
-
- getExecutor().execute(_runCompletWrite);
}
+ if (fillable)
+ getFillInterest().fillable();
+ getExecutor().execute(_runCompletWrite);
}
@Override
@@ -291,12 +291,12 @@ public class SslConnection extends AbstractConnection
// This means that a write of data has failed. Writes are done
// only if there is an active writeflusher or a read needed to write
// data. In either case the appropriate callback is passed on.
+ boolean failFiller = false;
synchronized (DecryptedEndPoint.this)
{
if (DEBUG)
LOG.debug("{} write.failed", SslConnection.this, x);
- if (_encryptedOutput != null)
- BufferUtil.clear(_encryptedOutput);
+ BufferUtil.clear(_encryptedOutput);
releaseEncryptedOutputBuffer();
_cannotAcceptMoreAppDataToFlush = false;
@@ -304,11 +304,12 @@ public class SslConnection extends AbstractConnection
if (_fillRequiresFlushToProgress)
{
_fillRequiresFlushToProgress = false;
- getFillInterest().onFail(x);
+ failFiller = true;
}
-
- getWriteFlusher().onFail(x);
}
+ if (failFiller)
+ getFillInterest().onFail(x);
+ getWriteFlusher().onFail(x);
}
};
@@ -344,6 +345,7 @@ public class SslConnection extends AbstractConnection
// all data could be wrapped. So either we need to write some encrypted data,
// OR if we are handshaking we need to read some encrypted data OR
// if neither then we should just try the flush again.
+ boolean flush = false;
synchronized (DecryptedEndPoint.this)
{
if (DEBUG)
@@ -355,19 +357,30 @@ public class SslConnection extends AbstractConnection
_cannotAcceptMoreAppDataToFlush = true;
getEndPoint().write(_writeCallback, _encryptedOutput);
}
+ // If we are handshaking and need to read,
else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
{
- // we are actually read blocked in order to write
- _flushRequiresFillToProgress=true;
+ // check if we are actually read blocked in order to write
+ _flushRequiresFillToProgress = true;
SslConnection.this.fillInterested();
}
- else if (isOutputShutdown())
+ else
+ {
+ flush = true;
+ }
+ }
+ if (flush)
+ {
+ // If the output is closed,
+ if (isOutputShutdown())
{
+ // don't bother writing, just notify of close
getWriteFlusher().onClose();
}
+ // Else,
else
{
- // try the flush again
+ // try to flush what is pending
getWriteFlusher().completeWrite();
}
}
@@ -413,9 +426,11 @@ public class SslConnection extends AbstractConnection
}
}
else
+ {
// Normal readable callback
// Get called back on onfillable when then is more data to fill
SslConnection.this.fillInterested();
+ }
return false;
}
@@ -490,15 +505,19 @@ public class SslConnection extends AbstractConnection
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+ Status unwrapResultStatus = unwrapResult.getStatus();
+ HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
// and deal with the results
- switch (unwrapResult.getStatus())
+ switch (unwrapResultStatus)
{
case BUFFER_OVERFLOW:
throw new IllegalStateException();
case CLOSED:
// Dang! we have to care about the handshake state specially for close
- switch (_sslEngine.getHandshakeStatus())
+ switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// We were not handshaking, so just tell the app we are closed
@@ -518,10 +537,28 @@ public class SslConnection extends AbstractConnection
throw new IllegalStateException();
default:
- if (unwrapResult.getStatus()==Status.BUFFER_UNDERFLOW)
- _underFlown=true;
+ if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} handshake completed client-side", SslConnection.this);
+ }
- // if we produced bytes, we don't care about the handshake state for now and it can be dealt with on another call to fill or flush
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ closeInbound();
+ return -1;
+ }
+
+ if (unwrapResultStatus == Status.BUFFER_UNDERFLOW)
+ _underFlown = true;
+
+ // If bytes were produced, don't bother with the handshake status;
+ // pass the decrypted data to the application, which will perform
+ // another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
if (app_in == buffer)
@@ -530,13 +567,15 @@ public class SslConnection extends AbstractConnection
}
// Dang! we have to care about the handshake state
- switch (_sslEngine.getHandshakeStatus())
+ switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// we just didn't read anything.
if (net_filled < 0)
- _sslEngine.closeInbound();
-
+ {
+ closeInbound();
+ return -1;
+ }
return 0;
case NEED_TASK:
@@ -548,7 +587,7 @@ public class SslConnection extends AbstractConnection
// we need to send some handshake data
// if we are called from flush
- if (buffer==__FLUSH_CALLED_FILL)
+ if (buffer == __FLUSH_CALLED_FILL)
return 0; // let it do the wrapping
_fillRequiresFlushToProgress = true;
@@ -565,14 +604,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)
{
@@ -622,6 +655,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
{
@@ -650,7 +695,6 @@ public class SslConnection extends AbstractConnection
while (true)
{
- // do the funky SSL thang!
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
BufferUtil.compact(_encryptedOutput);
int pos = BufferUtil.flipToFill(_encryptedOutput);
@@ -661,18 +705,20 @@ public class SslConnection extends AbstractConnection
if (wrapResult.bytesConsumed()>0)
consumed+=wrapResult.bytesConsumed();
- boolean all_consumed=true;
+ boolean allConsumed=true;
// clear empty buffers to prevent position creeping up the buffer
for (ByteBuffer b : appOuts)
{
if (BufferUtil.isEmpty(b))
BufferUtil.clear(b);
else
- all_consumed=false;
+ allConsumed=false;
}
+ Status wrapResultStatus = wrapResult.getStatus();
+
// and deal with the results returned from the sslEngineWrap
- switch (wrapResult.getStatus())
+ switch (wrapResultStatus)
{
case CLOSED:
// The SSL engine has close, but there may be close handshake that needs to be written
@@ -681,32 +727,51 @@ public class SslConnection extends AbstractConnection
_cannotAcceptMoreAppDataToFlush = true;
getEndPoint().flush(_encryptedOutput);
// If we failed to flush the close handshake then we will just pretend that
- // the write has progressed normally and let a subsequent call to flush (or WriteFlusher#onIncompleteFlushed)
- // to finish writing the close handshake. The caller will find out about the close on a subsequent flush or fill.
+ // the write has progressed normally and let a subsequent call to flush
+ // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+ // The caller will find out about the close on a subsequent flush or fill.
if (BufferUtil.hasContent(_encryptedOutput))
return false;
}
// otherwise we have written, and the caller will close the underlying connection
- return all_consumed;
+ return allConsumed;
case BUFFER_UNDERFLOW:
throw new IllegalStateException();
default:
if (DEBUG)
- LOG.debug("{} {} {}", this, wrapResult.getStatus(), BufferUtil.toDetailString(_encryptedOutput));
+ LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+ if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} handshake completed server-side", SslConnection.this);
+ }
+
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ shutdownOutput();
+ return allConsumed;
+ }
// if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_encryptedOutput))
getEndPoint().flush(_encryptedOutput);
// But we also might have more to do for the handshaking state.
- switch (_sslEngine.getHandshakeStatus())
+ switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// Return with the number of bytes consumed (which may be 0)
- return all_consumed&&BufferUtil.isEmpty(_encryptedOutput);
+ return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case NEED_TASK:
// run the task and continue
@@ -726,14 +791,13 @@ public class SslConnection extends AbstractConnection
_flushRequiresFillToProgress = true;
fill(__FLUSH_CALLED_FILL);
// Check if after the fill() we need to wrap again
- if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)
+ if (handshakeStatus == HandshakeStatus.NEED_WRAP)
continue;
}
- return all_consumed&&BufferUtil.isEmpty(_encryptedOutput);
+ return allConsumed&&BufferUtil.isEmpty(_encryptedOutput);
case FINISHED:
throw new IllegalStateException();
-
}
}
}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
index 9bb8729427..5c4bb4e8aa 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
@@ -22,7 +22,6 @@ package org.eclipse.jetty.io;
import java.util.concurrent.TimeoutException;
import junit.framework.Assert;
-
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
import org.junit.Before;
@@ -32,10 +31,10 @@ public class IdleTimeoutTest
{
volatile boolean _open;
volatile TimeoutException _expired;
-
+
TimerScheduler _timer;
IdleTimeout _timeout;
-
+
@Before
public void setUp() throws Exception
{
@@ -50,9 +49,9 @@ public class IdleTimeoutTest
{
_expired=timeout;
}
-
+
@Override
- protected boolean isOpen()
+ public boolean isOpen()
{
return _open;
}
@@ -65,7 +64,7 @@ public class IdleTimeoutTest
{
_open=false;
_timer.stop();
-
+
}
@Test
@@ -76,10 +75,10 @@ public class IdleTimeoutTest
Thread.sleep(100);
_timeout.notIdle();
}
-
+
Assert.assertNull(_expired);
}
-
+
@Test
public void testIdle() throws Exception
{
@@ -91,7 +90,7 @@ public class IdleTimeoutTest
Thread.sleep(1500);
Assert.assertNotNull(_expired);
}
-
+
@Test
public void testClose() throws Exception
{
@@ -104,7 +103,7 @@ public class IdleTimeoutTest
Thread.sleep(1500);
Assert.assertNull(_expired);
}
-
+
@Test
public void testClosed() throws Exception
{
@@ -153,7 +152,7 @@ public class IdleTimeoutTest
Thread.sleep(1000);
Assert.assertNotNull(_expired);
}
-
-
-
+
+
+
}
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 c1c8e4215d..77aea8b008 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;
@@ -73,10 +74,9 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
SSLEngine engine = __sslCtxFactory.newSSLEngine();
engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
-
+ sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint());
sslConnection.getDecryptedEndPoint().setConnection(appConnection);
-
return sslConnection;
}
@@ -197,10 +197,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-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
index 506d50bd5b..c02bd000ca 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -30,7 +30,6 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@@ -80,10 +79,9 @@ public class SslConnectionTest
SSLEngine engine = __sslCtxFactory.newSSLEngine();
engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine);
-
+ sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint());
sslConnection.getDecryptedEndPoint().setConnection(appConnection);
-
return sslConnection;
}
diff --git a/jetty-jsp/pom.xml b/jetty-jsp/pom.xml
index 8b93b34bdf..57377556c6 100644
--- a/jetty-jsp/pom.xml
+++ b/jetty-jsp/pom.xml
@@ -40,19 +40,19 @@
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.el</artifactId>
- <version>2.2.0.v201108011116</version>
+ <version>2.2.0.v201303151357</version>
</dependency>
<!-- EL Impl -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>com.sun.el</artifactId>
- <version>2.2.0.v201108011116</version>
+ <version>2.2.0.v201303151357</version>
</dependency>
<!-- Eclipse Java Compiler (for JSP Compilation) -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
- <version>3.7.1</version>
+ <version>3.8.2.v20130121</version>
</dependency>
</dependencies>
</project>
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 08b6f5c96f..e9c2382a28 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 0000000000..72c7da114f
--- /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 0000000000..ee63fa2a0b
--- /dev/null
+++ b/jetty-monitor/pom.xml
@@ -0,0 +1,145 @@
+<!--
+// ========================================================================
+// Copyright (c) Webtide LLC
+//
+// 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.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-project</artifactId>
+ <version>9.0.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jetty-monitor</artifactId>
+ <name>Jetty :: Monitoring</name>
+ <description>Performance monitoring artifact for jetty.</description>
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.monitor</bundle-symbolic-name>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Import-Package>javax.management.*,*</Import-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!--
+ Required for OSGI
+ -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>config</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>always</forkMode>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <configuration>
+ <onlyAnalyze>org.eclipse.jetty.monitor.*</onlyAnalyze>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-xml</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-jmx</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <!--dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-websocket</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency-->
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
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 0000000000..6a866dda28
--- /dev/null
+++ b/jetty-monitor/src/main/config/etc/jetty-monitor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+ <!-- Create Thread Monitor, and add to the Server as a lifecycle -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.monitor.ThreadMonitor">
+ <Set name="scanInterval">2000</Set>
+ <Set name="busyThreshold">90</Set>
+ <Set name="stackDepth">3</Set>
+ <Set name="trailLength">2</Set>
+ <!-- To enable logging CPU utilization for threads above specified threshold, -->
+ <!-- uncomment the following lines, changing log interval (in milliseconds) -->
+ <!-- and log threshold (in percent) as desired. -->
+ <!--
+ <Set name="logInterval">10000</Arg>
+ <Set name="logThreshold">1</Arg>
+ -->
+
+ <!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
+ <!-- uncomment the following lines. -->
+ <!--
+ <Set name="dumpable"><Ref id="Server"/></Set>
+ -->
+ </New>
+ </Arg>
+ </Call>
+</Configure>
+
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 0000000000..253a35240e
--- /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<MonitorAction> _actions = new HashSet<MonitorAction>();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * 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 0000000000..1bc74fb32d
--- /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<Long,ThreadMonitorInfo> _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<Long, ThreadMonitorInfo>();
+
+ 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<Thread,StackTraceElement[]> all = Thread.getAllStackTraces();
+
+ for (Map.Entry<Thread,StackTraceElement[]> 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<ThreadMonitorInfo> all = new ArrayList<ThreadMonitorInfo>();
+ for (int idx=0; idx<running.length; idx++)
+ {
+ ThreadMonitorInfo info = _monitorInfo.get(running[idx]);
+ if (info != null)
+ {
+ all.add(info);
+ }
+ }
+
+ // Sort selected thread objects by their CPU utilization
+ Collections.sort(all, new Comparator<ThreadMonitorInfo>()
+ {
+ /* ------------------------------------------------------------ */
+ 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 0000000000..99bbeab583
--- /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 <T> void exec(EventTrigger trigger, EventState<T> state, long timestamp)
+ {
+ Collection<TriggerState<T>> trs = state.values();
+
+ Properties data = new Properties();
+ for (TriggerState<T> 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<Object, Object> 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<ObjectName> 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 0000000000..4aa69be96d
--- /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 <kjkoster@gmail.com>
+ */
+@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<Thread.State, Integer> states = new HashMap<Thread.State, Integer>();
+
+ @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 0000000000..8c8abe4c77
--- /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 <TYPE extends Comparable<TYPE>>
+ extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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 0000000000..5452bf574c
--- /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 0000000000..c728937e70
--- /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 0000000000..3151d212a4
--- /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<TYPE>
+{
+
+ /* ------------------------------------------------------------ */
+ /**
+ * State
+ *
+ * Holds the state of a single {@link org.eclipse.jetty.monitor.jmx.EventTrigger event trigger}
+ */
+ public static class TriggerState<TYPE>
+ {
+ 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<String, TriggerState<TYPE>> _states;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructs an empty event state
+ */
+ public EventState()
+ {
+ _states = new ConcurrentHashMap<String, TriggerState<TYPE>>();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * 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<TYPE>(id, desc, value));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Adds a trigger state to the event state
+ *
+ * @param state trigger state to add
+ */
+ public void add(TriggerState<TYPE> 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<TriggerState<TYPE>> entries)
+ {
+ for (TriggerState<TYPE> 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<TYPE> 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<TriggerState<TYPE>> 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<TYPE> 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 0000000000..a49420a2af
--- /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 0000000000..b8484f4889
--- /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 0000000000..32ff52316e
--- /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 0000000000..f39553f2b5
--- /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<String,TimerTask> __tasks = new HashMap<String,TimerTask>();
+
+ 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 0000000000..8462a31795
--- /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<EventNotifier> _group;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a notifier group
+ */
+ public NotifierGroup()
+ {
+ _group = new HashSet<EventNotifier>();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve all event notifier associated with this group
+ *
+ * @return collection of event notifiers
+ */
+ public Collection<EventNotifier> 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<EventNotifier> 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<EventNotifier> 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 0000000000..cfc824fdbd
--- /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 0000000000..650d81f3ee
--- /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 0000000000..aa39b09889
--- /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 0000000000..8e85c38667
--- /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 0000000000..78a1e86935
--- /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<EventTrigger> _triggers;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct an event trigger
+ */
+ public AggregateEventTrigger()
+ {
+ _triggers = new ArrayList<EventTrigger>();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * 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<EventTrigger> triggers)
+ {
+ _triggers = new ArrayList<EventTrigger>(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<EventTrigger> 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 0000000000..5a31337f7c
--- /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<EventTrigger> _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<EventTrigger> 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 0000000000..4f792c4331
--- /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<TYPE extends Comparable<TYPE>>
+ 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<Long, EventState<TYPE>> _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<Long,EventState<TYPE>>();
+
+ _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<Long,EventState<TYPE>>();
+
+ _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<TYPE>)
+ * 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<TYPE>(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<TYPE> 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<TYPE> 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 0000000000..c945b48412
--- /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<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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 0000000000..1eadf0703d
--- /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<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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/GreaterThanOrEqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java
new file mode 100644
index 0000000000..89867573ef
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.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;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * GreaterThanOrEqualToAttrEventTrigger
+ *
+ * Event trigger that polls a value of an MXBean attribute and
+ * checks if it is greater than or equal to specified min value.
+ */
+public class GreaterThanOrEqualToAttrEventTrigger<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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 0000000000..4d0931c458
--- /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<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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/LessThanOrEqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java
new file mode 100644
index 0000000000..c811f6d999
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.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;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * LessThanOrEqualToAttrEventTrigger
+ *
+ * Event trigger that polls a value of an MXBean attribute and
+ * checks if it is less than or equal to specified max value.
+ */
+public class LessThanOrEqualToAttrEventTrigger<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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 0000000000..176c5e3547
--- /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<EventTrigger> _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<EventTrigger> 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 0000000000..7810c0fe3b
--- /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<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java
new file mode 100644
index 0000000000..868bd4c61d
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.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;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * RangeInclAttrEventTrigger
+ *
+ * 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 including the range bounds.
+ */
+public class RangeInclAttrEventTrigger<TYPE extends Comparable<TYPE>> extends AttrEventTrigger<TYPE>
+{
+ 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<TYPE> 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 0000000000..72d6060603
--- /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<Long> trigger =
+ new AttrEventTrigger<Long>("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<Long> trigger =
+ new EqualToAttrEventTrigger<Long>("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<Long> trigger =
+ new EqualToAttrEventTrigger<Long>("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<Long> trigger =
+ new GreaterThanAttrEventTrigger<Long>("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<Long> trigger =
+ new GreaterThanOrEqualToAttrEventTrigger<Long>("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<Long> trigger =
+ new LessThanAttrEventTrigger<Long>("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<Long> trigger =
+ new LessThanOrEqualToAttrEventTrigger<Long>("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<Long> trigger =
+ new RangeInclAttrEventTrigger<Long>("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<Long> trigger =
+ new RangeAttrEventTrigger<Long>("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<Long> trigger1 =
+ new GreaterThanAttrEventTrigger<Long>("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter",
+ testRangeLow);
+ LessThanOrEqualToAttrEventTrigger<Long> trigger2 =
+ new LessThanOrEqualToAttrEventTrigger<Long>("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<Long> trigger1 =
+ new LessThanOrEqualToAttrEventTrigger<Long>("org.eclipse.jetty.monitor:type=requestcounter,id=0", "counter",
+ testRangeLow);
+ GreaterThanAttrEventTrigger<Long> trigger2 =
+ new GreaterThanAttrEventTrigger<Long>("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<Long>
+ {
+ 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 0000000000..0dcd6f43ec
--- /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 0000000000..1cff36bf17
--- /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()<end)
+ {
+ for (int i=0;i<1000000000;i++)
+ result^=i;
+ }
+
+ if (result==42)
+ System.err.println("Bingo!");
+ }
+ }
+
+ private class Sleeper implements Runnable
+ {
+ private long _value;
+
+ /* ------------------------------------------------------------ */
+ public Sleeper(long value)
+ {
+ _value = value;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void run()
+ {
+ try
+ {
+ fn(_value);
+ }
+ catch (InterruptedException e) {}
+ }
+
+ /* ------------------------------------------------------------ */
+ public long fn(long value) throws InterruptedException
+ {
+ long result = value > 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 0000000000..e94eed36c1
--- /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 80deac4fb5..988ebb244f 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-osgi/jetty-osgi-npn/pom.xml b/jetty-osgi/jetty-osgi-npn/pom.xml
index 4df3c273b1..1d71cbb72b 100644
--- a/jetty-osgi/jetty-osgi-npn/pom.xml
+++ b/jetty-osgi/jetty-osgi-npn/pom.xml
@@ -1,4 +1,5 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
@@ -8,24 +9,40 @@
<artifactId>jetty-osgi-npn</artifactId>
<name>Jetty :: OSGi NPN Fragment</name>
<packaging>jar</packaging>
+ <properties>
+ <bundle-symbolic-name>org.eclipse.jetty.osgi.npn.fragment</bundle-symbolic-name>
+ </properties>
<build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <archive>
- <manifestEntries>
- <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
- <Bundle-SymbolicName>org.eclipse.jetty.osgi.npn.fragment;singleton:=true</Bundle-SymbolicName>
- <Bundle-Name>Jetty OSGi NPN Fragment</Bundle-Name>
- <Bundle-Version>9.0.0</Bundle-Version>
- <Export-Package>org.eclipse.jetty.npn;version="1.1.2"</Export-Package>
- <Fragment-Host>system.bundle;extension:=framework</Fragment-Host>
- </manifestEntries>
- </archive>
- </configuration>
- </plugin>
- </plugins>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.7</version>
+ <executions>
+ <execution>
+ <id>parse-version</id>
+ <goals>
+ <goal>parse-version</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
+ <Bundle-SymbolicName>${bundle-symbolic-name};singleton:=true</Bundle-SymbolicName>
+ <Bundle-Name>Jetty OSGi NPN Fragment</Bundle-Name>
+ <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>
+ <Export-Package>org.eclipse.jetty.npn;version="1.1.5"</Export-Package>
+ <Fragment-Host>system.bundle;extension:=framework</Fragment-Host>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
</build>
</project>
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index b9647e0757..b8822babf8 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -19,7 +19,7 @@
<felixversion>4.0.3</felixversion>
<injection.bundle.version>1.0</injection.bundle.version>
<runner.version>1.7.6</runner.version>
- <npn-version>1.1.2.v20130305</npn-version>
+ <npn-version>1.1.5.v20130313</npn-version>
</properties>
<dependencies>
<!-- Pax Exam Dependencies -->
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 caa3776249..d4298be1d9 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<Boolean> __IE6_BadOS = new ArrayTernaryTrie<>();
{
__IE6_BadOS.put("NT 5.01", Boolean.TRUE);
__IE6_BadOS.put("NT 5.0",Boolean.TRUE);
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
index a606a0c08b..7878479bc4 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -696,6 +696,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
+
if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
{
if (request.isSecure())
@@ -703,11 +704,13 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (httpConfig.getSecurePort() > 0)
{
- String url = httpConfig.getSecureScheme() + "://" + request.getServerName() + ":" + httpConfig.getSecurePort()
- + request.getRequestURI();
+ String scheme = httpConfig.getSecureScheme();
+ int port = httpConfig.getSecurePort();
+ String url = ("https".equalsIgnoreCase(scheme) && port==443)
+ ? "https://"+request.getServerName()+request.getRequestURI()
+ : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
if (request.getQueryString() != null)
url += "?" + request.getQueryString();
-
response.setContentLength(0);
response.sendRedirect(url);
}
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
index dc4b13b671..abbfe732e7 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
@@ -41,6 +41,8 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@@ -65,12 +67,14 @@ public class ConstraintTest
private Server _server;
private LocalConnector _connector;
private ConstraintSecurityHandler _security;
+ private HttpConfiguration _config;
@Before
public void startServer()
{
_server = new Server();
_connector = new LocalConnector(_server);
+ _config=_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration();
_server.setConnectors(new Connector[]{_connector});
ContextHandler _context = new ContextHandler();
@@ -161,7 +165,15 @@ public class ConstraintTest
mapping5.setConstraint(constraint5);
mapping5.setMethod("POST");
- return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5);
+ Constraint constraint6 = new Constraint();
+ constraint6.setAuthenticate(false);
+ constraint6.setName("data constraint");
+ constraint6.setDataConstraint(2);
+ ConstraintMapping mapping6 = new ConstraintMapping();
+ mapping6.setPathSpec("/data/*");
+ mapping6.setConstraint(constraint6);
+
+ return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6);
}
@Test
@@ -742,9 +754,9 @@ public class ConstraintTest
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
- response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\nHost:wibble.com:8888\r\n\r\n");
assertThat(response,containsString(" 302 Found"));
- assertThat(response,containsString("/ctx/testLoginPage"));
+ assertThat(response,containsString("http://wibble.com:8888/ctx/testLoginPage"));
String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
@@ -840,6 +852,48 @@ public class ConstraintTest
assertThat(response,startsWith("HTTP/1.1 200 OK"));
}
+
+
+ @Test
+ public void testDataRedirection() throws Exception
+ {
+ _security.setAuthenticator(new BasicAuthenticator());
+ _server.start();
+
+ String response;
+
+ response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
+ assertTrue(response.startsWith("HTTP/1.1 403"));
+
+ _config.setSecurePort(8443);
+ _config.setSecureScheme("https");
+
+ response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
+ assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertTrue(response.indexOf("Location") > 0);
+ assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
+
+ _config.setSecurePort(443);
+ response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
+ assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertTrue(response.indexOf("Location") > 0);
+ assertTrue(response.indexOf(":443/ctx/data/info") < 0);
+
+ _config.setSecurePort(8443);
+ response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
+ assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertTrue(response.indexOf("Location") > 0);
+ assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
+
+ _config.setSecurePort(443);
+ response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
+ System.err.println(response);
+ assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertTrue(response.indexOf("Location") > 0);
+ assertTrue(response.indexOf(":443") < 0);
+ assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0);
+ }
+
@Test
public void testRoleRef() throws Exception
{
diff --git a/jetty-server/src/main/config/etc/jetty-https.xml b/jetty-server/src/main/config/etc/jetty-https.xml
index fe3f1952f8..a6bef16ff1 100644
--- a/jetty-server/src/main/config/etc/jetty-https.xml
+++ b/jetty-server/src/main/config/etc/jetty-https.xml
@@ -2,56 +2,13 @@
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- ============================================================= -->
-<!-- Configure the Jetty Server instance with an ID "Server" -->
-<!-- by adding a HTTPS connector. -->
+<!-- Configure a HTTPS connector. -->
<!-- This configuration must be used in conjunction with jetty.xml -->
-<!-- It should not be used with jetty-spdy.xml which can provide -->
-<!-- both HTTPS and SPDY connections -->
+<!-- and jetty-ssl.xml. -->
<!-- ============================================================= -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
- <!-- Setup the SSL Context factory used to establish all TLS -->
- <!-- Connections and session. -->
- <!-- -->
- <!-- Consult the javadoc of o.e.j.util.ssl.SslContextFactory -->
- <!-- o.e.j.server.HttpConnectionFactory for all configuration -->
- <!-- that may be set here. -->
- <!-- =========================================================== -->
- <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
- <Set name="KeyStorePath"><Property name="jetty.home" default="." />/etc/keystore</Set>
- <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
- <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
- <Set name="TrustStorePath"><Property name="jetty.home" default="." />/etc/keystore</Set>
- <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
- <Set name="ExcludeCipherSuites">
- <Array type="String">
- <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
- <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
- <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
- <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
- <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
- <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
- <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
- </Array>
- </Set>
- </New>
-
- <!-- =========================================================== -->
- <!-- Create a TLS specific HttpConfiguration based on the -->
- <!-- common HttpConfiguration defined in jetty.xml -->
- <!-- Add a SecureRequestCustomizer to extract certificate and -->
- <!-- session information -->
- <!-- =========================================================== -->
- <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
- <Arg><Ref refid="httpConfig"/></Arg>
- <Call name="addCustomizer">
- <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
- </Call>
- </New>
-
-
- <!-- =========================================================== -->
<!-- Add a HTTPS Connector. -->
<!-- Configure an o.e.j.server.ServerConnector with connection -->
<!-- factories for TLS (aka SSL) and HTTP to provide HTTPS. -->
@@ -62,7 +19,7 @@
<!-- o.e.j.server.HttpConnectionFactory for all configuration -->
<!-- that may be set here. -->
<!-- =========================================================== -->
- <Call id="sslConnector" name="addConnector">
+ <Call id="httpsConnector" name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server"><Ref refid="Server" /></Arg>
@@ -82,7 +39,7 @@
</Array>
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
- <Set name="port"><Property name="jetty.tls.port" default="8443" /></Set>
+ <Set name="port"><Property name="jetty.https.port" default="8443" /></Set>
<Set name="idleTimeout">30000</Set>
</New>
</Arg>
diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml
index 984eb6a887..6e6eb054f0 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 @@
<Arg>
<New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
<Set name="requestLog">
- <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+ <New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
<Set name="filename"><Property name="jetty.logs" default="./logs" />/yyyy_mm_dd.request.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
- <Set name="extended">false</Set>
+ <Set name="extended">true</Set>
<Set name="logCookies">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
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 0000000000..b4c3551aad
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-ssl.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure a TLS (SSL) Context Factory -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and either jetty-https.xml or jetty-spdy.xml (but not both) -->
+<!-- ============================================================= -->
+<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+ <Set name="KeyStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.keystore" default="etc/keystore"/></Set>
+ <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+ <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
+ <Set name="TrustStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.truststore" default="etc/keystore"/></Set>
+ <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+ <Set name="EndpointIdentificationAlgorithm"></Set>
+ <Set name="ExcludeCipherSuites">
+ <Array type="String">
+ <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
+ <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+ <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+ <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
+ </Array>
+ </Set>
+
+ <!-- =========================================================== -->
+ <!-- Create a TLS specific HttpConfiguration based on the -->
+ <!-- common HttpConfiguration defined in jetty.xml -->
+ <!-- Add a SecureRequestCustomizer to extract certificate and -->
+ <!-- session information -->
+ <!-- =========================================================== -->
+ <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+ <Arg><Ref refid="httpConfig"/></Arg>
+ <Call name="addCustomizer">
+ <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+ </Call>
+ </New>
+
+</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index dd78c54004..2d9b8596e4 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -44,8 +44,14 @@
<!-- =========================================================== -->
<Arg name="threadpool">
<New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
- <Set name="minThreads">10</Set>
- <Set name="maxThreads">200</Set>
+ <Arg name="minThreads" type="int">10</Arg>
+ <Arg name="maxThreads" type="int">200</Arg>
+ <Arg name="idleTimeout" type="int">60000</Arg>
+ <!-- Arg >
+ <New class="org.eclipse.jetty.util.ConcurrentArrayBlockingQueue$Unbounded">
+ <Arg type='int'>32</Arg>
+ </New>
+ </Arg -->
<Set name="detailedDump">false</Set>
</New>
</Arg>
@@ -76,7 +82,7 @@
<!-- =========================================================== -->
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Set name="secureScheme">https</Set>
- <Set name="securePort"><Property name="jetty.tls.port" default="8443" /></Set>
+ <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
<Set name="outputBufferSize">32768</Set>
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
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 0000000000..5223e97004
--- /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<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+ {
+ @Override
+ protected StringBuilder initialValue()
+ {
+ return new StringBuilder(256);
+ }
+ };
+
+
+ private String[] _ignorePaths;
+ private boolean _extended;
+ private transient PathMap<String> _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 0000000000..047e3b6d40
--- /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<String> _queue;
+ private transient WriterThread _thread;
+ private boolean _warnedFull;
+
+ public AsyncNCSARequestLog()
+ {
+ this(null,null);
+ }
+
+ public AsyncNCSARequestLog(BlockingQueue<String> queue)
+ {
+ this(null,queue);
+ }
+
+ public AsyncNCSARequestLog(String filename)
+ {
+ this(filename,null);
+ }
+
+ public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
+ {
+ super(filename);
+ if (queue==null)
+ queue=new ConcurrentArrayBlockingQueue.Bounded<String>(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/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index f0e4c21287..9fbbde7ab5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -253,7 +253,16 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
}
else
{
- _request.setDispatcherType(DispatcherType.ASYNC);
+ if (_request.getHttpChannelState().isExpired())
+ {
+ _request.setDispatcherType(DispatcherType.ERROR);
+ _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+ _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
+ _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+ _response.setStatusWithReason(500,"Async Timeout");
+ }
+ else
+ _request.setDispatcherType(DispatcherType.ASYNC);
getServer().handleAsync(this);
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
index 233e2f28b9..e50f77ad4e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -413,12 +413,12 @@ public class HttpChannelState implements AsyncContext
{
case ASYNCSTARTED:
case ASYNCWAIT:
+ _expired=true;
aListeners=_asyncListeners;
break;
default:
return;
}
- _expired=true;
}
if (aListeners!=null)
@@ -436,23 +436,20 @@ public class HttpChannelState implements AsyncContext
}
}
- boolean complete;
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED:
case ASYNCWAIT:
- complete = true;
+ _state=State.REDISPATCH;
break;
default:
- complete = false;
+ _expired=false;
break;
}
}
- if (complete)
- complete();
-
+
scheduleDispatch();
}
@@ -597,6 +594,14 @@ public class HttpChannelState implements AsyncContext
}
}
+ public boolean isExpired()
+ {
+ synchronized (this)
+ {
+ return _expired;
+ }
+ }
+
public boolean isInitial()
{
synchronized(this)
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 1bfd730fb2..ef11b3e0c7 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)
- {
- _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()
+ @Override
+ protected boolean isEnabled()
{
- return _logTimeZone;
+ return (_fileOut != null);
}
/* ------------------------------------------------------------ */
@@ -245,30 +155,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
/* ------------------------------------------------------------ */
/**
- * 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.
*
* @param append true - request log file will be appended after restart,
@@ -293,121 +179,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
/* ------------------------------------------------------------ */
/**
- * 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.
* @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
*
@@ -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)
- {
- _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)
+ @Override
+ public void write(String requestEntry) throws IOException
{
- try
+ synchronized(this)
{
- if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
- return;
-
- if (_fileOut == null)
+ if (_writer==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();
- }
+ _writer.write(requestEntry.toString());
+ _writer.write(StringUtil.__LINE_SEPARATOR);
+ _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('"');
- }
- }
-
+
/* ------------------------------------------------------------ */
/**
* 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 a04b74d42b..f2518ff90c 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
@@ -1768,6 +1768,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/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
index 096c42f3dd..d1e90a23f3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -334,8 +334,10 @@ public class Response implements HttpServletResponse
setStatus(code);
_reason=message;
+ Request request = _channel.getRequest();
+ Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
if (message==null)
- message=HttpStatus.getMessage(code);
+ message=cause==null?HttpStatus.getMessage(code):cause.toString();
// If we are allowed to have a body
if (code!=SC_NO_CONTENT &&
@@ -343,7 +345,6 @@ public class Response implements HttpServletResponse
code!=SC_PARTIAL_CONTENT &&
code>=SC_OK)
{
- Request request = _channel.getRequest();
ErrorHandler error_handler = null;
ContextHandler.Context context = request.getContext();
@@ -383,7 +384,6 @@ public class Response implements HttpServletResponse
writer.write(Integer.toString(code));
writer.write(' ');
if (message==null)
- message=HttpStatus.getMessage(code);
writer.write(message);
writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
writer.write(Integer.toString(code));
@@ -625,13 +625,21 @@ public class Response implements HttpServletResponse
if (sc <= 0)
throw new IllegalArgumentException();
if (!isIncluding())
+ {
_status = sc;
+ _reason = null;
+ }
}
@Override
@Deprecated
public void setStatus(int sc, String sm)
{
+ setStatusWithReason(sc,sm);
+ }
+
+ public void setStatusWithReason(int sc, String sm)
+ {
if (sc <= 0)
throw new IllegalArgumentException();
if (!isIncluding())
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 930f755255..e8e03113ad 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());
@@ -437,7 +439,7 @@ public class Server extends HandlerWrapper implements Attributes
final Response response=connection.getResponse();
if (LOG.isDebugEnabled())
- LOG.debug("REQUEST "+target+" on "+connection);
+ LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
if ("*".equals(target))
{
@@ -498,7 +500,7 @@ public class Server extends HandlerWrapper implements Attributes
if (LOG.isDebugEnabled())
{
- LOG.debug("REQUEST "+target+" on "+connection);
+ LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
handle(target, baseRequest, request, response);
LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
}
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 405ce12f6f..6d4f9e1915 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;
* <p>
* 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,166 @@ 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;
+ }
+ if (DEBUG)
+ 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 +230,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 +304,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 +312,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");
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.port = port;
}
- this.port = port;
}
- public void start()
+ protected void start() throws Exception
{
- if (isAlive())
- {
- System.err.printf("ShutdownMonitor already started");
- return; // cannot start it again
- }
- startListenSocket();
- if (serverSocket == null)
+ ShutdownMonitorThread t = null;
+ synchronized (this)
{
- return;
+ if (thread != null && thread.isAlive())
+ {
+ System.err.printf("ShutdownMonitorThread already started");
+ return; // cannot start it again
+ }
+
+ thread = new ShutdownMonitorThread();
+ t = thread;
}
-
- super.start();
+
+ if (t != null)
+ t.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)
- {
- // server assigned port in use
- this.port = serverSocket.getLocalPort();
- System.out.printf("STOP.PORT=%d%n",this.port);
- }
-
- 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
+ protected boolean isAlive ()
+ {
+ boolean result = false;
+ synchronized (this)
{
- // establish the port and key that are in use
- debug("STOP.PORT=%d",this.port);
- debug("STOP.KEY=%s",this.key);
- debug("%s",serverSocket);
+ 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 0000000000..3c655d3116
--- /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/SslConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
index da83f1f96d..d3e9fd6c78 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
@@ -78,6 +78,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
engine.setUseClientMode(false);
SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
configure(sslConnection, connector, endPoint);
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index cb2d54fe2b..4d15352b28 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -941,7 +941,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (old_context != _scontext)
{
// check the target.
- if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
+ if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isExpired()))
{
if (_compactPath)
target = URIUtil.compactPath(target);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
index f76cfb8641..9830df24f2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
@@ -214,6 +214,12 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
checkValid();
return _lastAccessed;
}
+
+ /* ------------------------------------------------------------- */
+ public void setLastAccessedTime(long time)
+ {
+ _lastAccessed = time;
+ }
/* ------------------------------------------------------------- */
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
index 1cfaada25f..e3df34f98c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
@@ -37,6 +37,7 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
protected Random _random;
protected boolean _weakRandom;
protected String _workerName;
+ protected long _reseed=100000L;
/* ------------------------------------------------------------ */
public AbstractSessionIdManager()
@@ -91,6 +92,24 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
/* ------------------------------------------------------------ */
/**
+ * @return the reseed probability
+ */
+ public long getReseed()
+ {
+ return _reseed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the reseed probability.
+ * @param reseed If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+ */
+ public void setReseed(long reseed)
+ {
+ _reseed = reseed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
* Create a new session id if necessary.
*
* @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
@@ -134,19 +153,37 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
long r0=_weakRandom
?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
:_random.nextLong();
- if (r0<0)
- r0=-r0;
- long r1=_weakRandom
- ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
- :_random.nextLong();
- if (r1<0)
- r1=-r1;
- id=Long.toString(r0,36)+Long.toString(r1,36);
-
- //add in the id of the node to ensure unique id across cluster
- //NOTE this is different to the node suffix which denotes which node the request was received on
- if (_workerName!=null)
- id=_workerName + id;
+ if (r0<0)
+ r0=-r0;
+
+ // random chance to reseed
+ if (_reseed>0 && (r0%_reseed)== 1L)
+ {
+ LOG.debug("Reseeding {}",this);
+ if (_random instanceof SecureRandom)
+ {
+ SecureRandom secure = (SecureRandom)_random;
+ secure.setSeed(secure.generateSeed(8));
+ }
+ else
+ {
+ _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
+ }
+ }
+
+ long r1=_weakRandom
+ ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+ :_random.nextLong();
+ if (r1<0)
+ r1=-r1;
+
+ id=Long.toString(r0,36)+Long.toString(r1,36);
+
+ //add in the id of the node to ensure unique id across cluster
+ //NOTE this is different to the node suffix which denotes which node the request was received on
+ if (_workerName!=null)
+ id=_workerName + id;
+
}
return id;
}
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 ff7a2d9f96..1954894aaf 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,69 +589,55 @@ 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();
- if (size>0)
- {
- ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
- for (int i=0; i<size;i++)
- {
- String key = ois.readUTF();
- Object value = ois.readObject();
- session.setAttribute(key,value);
- }
- ois.close();
- }
- else
- in.close();
- return session;
- }
+ int size = di.readInt();
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- protected class ClassLoadingObjectInputStream extends ObjectInputStream
- {
- /* ------------------------------------------------------------ */
- public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+ restoreSessionAttributes(di, size, session);
+
+ try
{
- super(in);
+ int maxIdle = di.readInt();
+ session.setMaxInactiveInterval(maxIdle);
}
-
- /* ------------------------------------------------------------ */
- public ClassLoadingObjectInputStream () throws IOException
+ catch (EOFException e)
{
- super();
+ LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
+ LOG.ignore(e);
}
- /* ------------------------------------------------------------ */
- @Override
- public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+ return session;
+ }
+
+
+ private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
+ throws Exception
+ {
+ if (size>0)
{
- try
- {
- return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
- }
- catch (ClassNotFoundException e)
+ ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is);
+
+ for (int i=0; i<size;i++)
{
- return super.resolveClass(cl);
+ String key = ois.readUTF();
+ Object value = ois.readObject();
+ session.setAttribute(key,value);
}
}
}
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 ce1b731824..15e6022499 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 ea92acde9c..d81b4468ff 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;
@@ -62,6 +63,7 @@ import org.eclipse.jetty.util.log.Logger;
public class JDBCSessionIdManager extends AbstractSessionIdManager
{
final static Logger LOG = SessionHandler.LOG;
+ public final static int MAX_INTERVAL_NOT_SET = -999;
protected final HashSet<String> _sessionIds = new HashSet<String>();
protected Server _server;
@@ -73,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
@@ -85,7 +88,6 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
protected String _createSessionTable;
protected String _selectBoundedExpiredSessions;
- protected String _deleteOldExpiredSessions;
protected String _insertId;
protected String _deleteId;
@@ -324,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)
@@ -429,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);
@@ -567,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());
}
/**
@@ -629,13 +634,15 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
* Set up the tables in the database
* @throws SQLException
*/
+ /**
+ * @throws SQLException
+ */
private void prepareTables()
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 = ?";
@@ -671,9 +678,38 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
_createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+
" contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
" lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
- " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
+ " lastSavedTime "+longType+", expiryTime "+longType+", maxInterval "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
connection.createStatement().executeUpdate(_createSessionTable);
}
+ else
+ {
+ //session table exists, check it has maxinterval column
+ ResultSet colResult = null;
+ try
+ {
+ colResult = metaData.getColumns(null, null,_dbAdaptor.convertIdentifier(_sessionTable), _dbAdaptor.convertIdentifier("maxInterval"));
+ }
+ catch (SQLException s)
+ {
+ LOG.warn("Problem checking if "+_sessionTable+" table contains maxInterval column. Ensure table contains column definition: \"maxInterval long not null default -999\"");
+ throw s;
+ }
+
+ if (!colResult.next())
+ {
+ try
+ {
+ //add the maxinterval column
+ String longType = _dbAdaptor.getLongType();
+ connection.createStatement().executeUpdate("alter table "+_sessionTable+" add maxInterval "+longType+" not null default "+MAX_INTERVAL_NOT_SET);
+ }
+ catch (SQLException s)
+ {
+ LOG.warn("Problem adding maxInterval column. Ensure table contains column definition: \"maxInterval long not null default -999\"");
+ throw s;
+ }
+ }
+ }
//make some indexes on the JettySessions table
String index1 = "idx_"+_sessionTable+"_expiry";
@@ -701,20 +737,20 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
//set up some strings representing the statements for session manipulation
_insertSession = "insert into "+_sessionTable+
- " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
- " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, maxInterval, map) "+
+ " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
_deleteSession = "delete from "+_sessionTable+
" where "+_sessionTableRowId+" = ?";
_updateSession = "update "+_sessionTable+
- " set sessionId = ?, lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?";
+ " set sessionId = ?, lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, maxInterval = ?, map = ? where "+_sessionTableRowId+" = ?";
_updateSessionNode = "update "+_sessionTable+
" set lastNode = ? where "+_sessionTableRowId+" = ?";
_updateSessionAccessTime = "update "+_sessionTable+
- " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?";
+ " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, maxInterval = ? where "+_sessionTableRowId+" = ?";
}
@@ -824,57 +860,79 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
private void scavenge ()
{
Connection connection = null;
- List<String> expiredSessionIds = new ArrayList<String>();
+ Set<String> expiredSessionIds = new HashSet<String>();
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<contexts.length; i++)
- {
-
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
- if (sessionHandler != null)
+ //Pass 2: find sessions that have expired a while ago for which this node was their last manager
+ PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions);
+ expiredSessionIds.clear();
+ upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+ if (upperBound > 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())
{
- SessionManager manager = sessionHandler.getSessionManager();
- if (manager != null && manager instanceof JDBCSessionManager)
- {
- ((JDBCSessionManager)manager).expire(expiredSessionIds);
- }
+ 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);
}
- //find all sessions that have expired at least a couple of scanIntervals ago and just delete them
- upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+
+ //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");
+ 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);
}
}
}
@@ -888,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)
{
@@ -903,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<String> expiredSessionIds = new ArrayList<String>();
- 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<String> expiredSessionIds, boolean forceDelete)
+ {
+ Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
{
- String sessionId = result.getString("sessionId");
- expiredSessionIds.add(sessionId);
- if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId);
+ SessionManager manager = sessionHandler.getSessionManager();
+ if (manager != null && manager instanceof JDBCSessionManager)
+ {
+ Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
+ if (successfullyExpiredIds != null)
+ remainingIds.removeAll(successfullyExpiredIds);
+ }
}
-
- Statement sessionsTableStatement = null;
- Statement sessionIdsTableStatement = null;
+ }
- if (!expiredSessionIds.isEmpty())
+ //Any remaining ids are of those sessions that no context removed
+ if (!remainingIds.isEmpty() && forceDelete)
+ {
+ LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
+ try
{
- sessionsTableStatement = connection.createStatement();
- sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds));
- sessionIdsTableStatement = connection.createStatement();
- sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds));
+ //ensure they aren't in the local list of in-use session ids
+ synchronized (_sessionIds)
+ {
+ _sessionIds.removeAll(remainingIds);
+ }
+
+ cleanExpiredSessionIds(remainingIds);
}
- connection.commit();
+ catch (Exception e)
+ {
+ LOG.warn("Error removing expired session ids", e);
+ }
+ }
+ }
+
- synchronized (_sessionIds)
+
+
+ private void cleanExpiredSessionIds (Set<String> 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)
{
- _sessionIds.removeAll(expiredSessionIds); //in case they were in our local cache of session ids
+ 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 (connection != null)
- connection.rollback();
- throw e;
+ if (con != null)
+ {
+ con.rollback();
+ throw e;
+ }
}
finally
{
- try
- {
- if (connection != null)
- connection.close();
- }
- catch (SQLException e)
+ if (con != null)
{
- LOG.warn(e);
+ con.close();
}
}
}
+
/**
*
* @param sql
- * @param connection
- * @param expiredSessionIds
+ * @param atoms
* @throws Exception
*/
- private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds)
+ private String fillInClause (String sql, String[] literals, int start, int end)
throws Exception
{
StringBuffer buff = new StringBuffer();
buff.append(sql);
buff.append("(");
- Iterator<String> itor = expiredSessionIds.iterator();
- while (itor.hasNext())
+ for (int i=start; i<end; i++)
{
- buff.append("'"+(itor.next())+"'");
- if (itor.hasNext())
+ buff.append("'"+(literals[i])+"'");
+ if (i+1<end)
buff.append(",");
}
buff.append(")");
-
- if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff);
return buff.toString();
}
+
+
private void initializeDatabase ()
throws Exception
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
index 5bf6595b2f..afe7ae2c0a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -29,10 +29,10 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
@@ -42,6 +42,7 @@ import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -77,96 +78,149 @@ public class JDBCSessionManager extends AbstractSessionManager
protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+
+
+
/**
- * SessionData
+ * Session
*
- * Persistable data about a session.
+ * Session instance.
*/
- public class SessionData
+ public class Session extends AbstractSession
{
- private String _id;
- private String _rowId;
- private long _accessed;
- private long _lastAccessed;
- private long _maxIdleMs=-1;
+ private static final long serialVersionUID = 5208464051134226143L;
+
+ /**
+ * If dirty, session needs to be (re)persisted
+ */
+ private boolean _dirty=false;
+
+
+ /**
+ * Time in msec since the epoch that a session cookie was set for this session
+ */
private long _cookieSet;
- private long _created;
- private Map<String,Object> _attributes;
- private String _lastNode;
- private String _canonicalContext;
- private long _lastSaved;
+
+
+ /**
+ * Time in msec since the epoch that the session will expire
+ */
private long _expiryTime;
+
+
+ /**
+ * Time in msec since the epoch that the session was last persisted
+ */
+ private long _lastSaved;
+
+
+ /**
+ * Unique identifier of the last node to host the session
+ */
+ private String _lastNode;
+
+
+ /**
+ * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
+ */
private String _virtualHost;
-
- public SessionData (String sessionId)
+
+
+ /**
+ * Unique row in db for session
+ */
+ private String _rowId;
+
+
+ /**
+ * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
+ */
+ private String _canonicalContext;
+
+
+ /**
+ * Session from a request.
+ *
+ * @param request
+ */
+ protected Session (HttpServletRequest request)
{
- _id=sessionId;
- _created=System.currentTimeMillis();
- _accessed = _created;
- _attributes = new HashMap<String,Object>();
+ super(JDBCSessionManager.this,request);
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ _virtualHost = JDBCSessionManager.getVirtualHost(_context);
+ _canonicalContext = canonicalize(_context.getContextPath());
_lastNode = getSessionIdManager().getWorkerName();
}
-
- public SessionData (String sessionId,Map<String,Object> attributes)
+
+
+ /**
+ * Session restored from database
+ * @param sessionId
+ * @param rowId
+ * @param created
+ * @param accessed
+ */
+ protected Session (String sessionId, String rowId, long created, long accessed, long maxInterval)
{
- _id=sessionId;
- _created=System.currentTimeMillis();
- _accessed = _created;
- _attributes = attributes;
- _lastNode = getSessionIdManager().getWorkerName();
+ super(JDBCSessionManager.this, created, accessed, sessionId);
+ _rowId = rowId;
+ super.setMaxInactiveInterval((int)maxInterval); //restore the session's previous inactivity interval setting
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
}
-
- public synchronized String getId ()
+
+
+ protected synchronized String getRowId()
{
- return _id;
+ return _rowId;
}
- public synchronized void setId (String id)
+ protected synchronized void setRowId(String rowId)
{
- _id = id;
+ _rowId = rowId;
}
-
- public synchronized long getCreated ()
+
+ public synchronized void setVirtualHost (String vhost)
{
- return _created;
+ _virtualHost=vhost;
}
- protected synchronized void setCreated (long ms)
+ public synchronized String getVirtualHost ()
{
- _created = ms;
+ return _virtualHost;
}
-
- public synchronized long getAccessed ()
+
+ public synchronized long getLastSaved ()
{
- return _accessed;
+ return _lastSaved;
}
- protected synchronized void setAccessed (long ms)
+ public synchronized void setLastSaved (long time)
{
- _accessed = ms;
+ _lastSaved=time;
}
-
- public synchronized void setMaxIdleMs (long ms)
+ public synchronized void setExpiryTime (long time)
{
- _maxIdleMs = ms;
+ _expiryTime=time;
}
- public synchronized long getMaxIdleMs()
+ public synchronized long getExpiryTime ()
{
- return _maxIdleMs;
+ return _expiryTime;
}
+
- public synchronized void setLastAccessed (long ms)
+ public synchronized void setCanonicalContext(String str)
{
- _lastAccessed = ms;
+ _canonicalContext=str;
}
- public synchronized long getLastAccessed()
+ public synchronized String getCanonicalContext ()
{
- return _lastAccessed;
+ return _canonicalContext;
}
-
+
public void setCookieSet (long ms)
{
_cookieSet = ms;
@@ -177,26 +231,6 @@ public class JDBCSessionManager extends AbstractSessionManager
return _cookieSet;
}
- public synchronized void setRowId (String rowId)
- {
- _rowId=rowId;
- }
-
- protected synchronized String getRowId()
- {
- return _rowId;
- }
-
- protected synchronized Map<String,Object> getAttributeMap ()
- {
- return _attributes;
- }
-
- protected synchronized void setAttributeMap (Map<String,Object> map)
- {
- _attributes = map;
- }
-
public synchronized void setLastNode (String node)
{
_lastNode=node;
@@ -207,172 +241,76 @@ public class JDBCSessionManager extends AbstractSessionManager
return _lastNode;
}
- public synchronized void setCanonicalContext(String str)
- {
- _canonicalContext=str;
- }
-
- public synchronized String getCanonicalContext ()
- {
- return _canonicalContext;
- }
-
- public synchronized long getLastSaved ()
- {
- return _lastSaved;
- }
-
- public synchronized void setLastSaved (long time)
- {
- _lastSaved=time;
- }
-
- public synchronized void setExpiryTime (long time)
- {
- _expiryTime=time;
- }
-
- public synchronized long getExpiryTime ()
- {
- return _expiryTime;
- }
-
- public synchronized void setVirtualHost (String vhost)
+ @Override
+ public void setAttribute (String name, Object value)
{
- _virtualHost=vhost;
+ super.setAttribute(name, value);
+ _dirty=true;
}
- public synchronized String getVirtualHost ()
+ @Override
+ public void removeAttribute (String name)
{
- return _virtualHost;
+ super.removeAttribute(name);
+ _dirty=true;
}
@Override
- public String toString ()
+ protected void cookieSet()
{
- return "Session rowId="+_rowId+",id="+_id+",lastNode="+_lastNode+
- ",created="+_created+",accessed="+_accessed+
- ",lastAccessed="+_lastAccessed+",cookieSet="+_cookieSet+
- "lastSaved="+_lastSaved;
+ _cookieSet = getAccessed();
}
- }
-
-
- /**
- * Session
- *
- * Session instance in memory of this node.
- */
- public class Session extends AbstractSession
- {
- private static final long serialVersionUID = 5208464051134226143L;
- private final SessionData _data;
- private boolean _dirty=false;
-
-
/**
- * Session from a request.
+ * Entry to session.
+ * Called by SessionHandler on inbound request and the session already exists in this node's memory.
*
- * @param request
- */
- protected Session (HttpServletRequest request)
- {
- super(JDBCSessionManager.this,request);
- _data = new SessionData(getClusterId(),getAttributeMap());
- if (_dftMaxIdleSecs>0)
- _data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
- _data.setCanonicalContext(canonicalize(_context.getContextPath()));
- _data.setVirtualHost(getVirtualHost(_context));
- int maxInterval=getMaxInactiveInterval();
- _data.setExpiryTime(maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
- }
-
- /**
- * Session restored in database.
- * @param data
+ * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
*/
- protected Session (long accessed, SessionData data)
- {
- super(JDBCSessionManager.this,data.getCreated(), accessed, data.getId());
- _data=data;
- if (_dftMaxIdleSecs>0)
- _data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
- addAttributes(_data.getAttributeMap());
- _data.setAttributeMap(getAttributeMap());
- }
-
- @Override
- public void setAttribute (String name, Object value)
- {
- super.setAttribute(name, value);
- _dirty=true;
- }
-
- @Override
- public void removeAttribute (String name)
- {
- super.removeAttribute(name);
- _dirty=true;
- }
-
-
-
- @Override
- protected void setClusterId(String clusterId)
- {
- super.setClusterId(clusterId);
- _data.setId(clusterId);
- _dirty = true;
- }
-
- @Override
- protected void setNodeId(String nodeId)
- {
- _data.setLastNode(nodeId);
- super.setNodeId(nodeId);
- _dirty = true;
- }
-
- protected void save() throws Exception
+ @Override
+ protected boolean access(long time)
{
- try
+ synchronized (this)
{
- updateSession(_data);
- }
- finally
- {
- _dirty = false;
+ if (super.access(time))
+ {
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+ return true;
+ }
+ return false;
}
}
+
+
+
- @Override
- protected void cookieSet()
- {
- _data.setCookieSet(_data.getAccessed());
- }
- /**
- * Entry to session.
- * Called by SessionHandler on inbound request and the session already exists in this node's memory.
- *
- * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+ /**
+ * Change the max idle time for this session. This recalculates the expiry time.
+ * @see org.eclipse.jetty.server.session.AbstractSession#setMaxInactiveInterval(int)
*/
@Override
- protected boolean access(long time)
+ public void setMaxInactiveInterval(int secs)
{
- if (super.access(time))
+ synchronized (this)
{
- _data.setLastAccessed(_data.getAccessed());
- _data.setAccessed(time);
-
+ super.setMaxInactiveInterval(secs);
int maxInterval=getMaxInactiveInterval();
- _data.setExpiryTime(maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
- return true;
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ //force the session to be written out right now
+ try
+ {
+ updateSessionAccessTime(this);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem saving changed max idle time for session "+ this, e);
+ }
}
- return false;
}
+
/**
* Exit from session
* @see org.eclipse.jetty.server.session.AbstractSession#complete()
@@ -380,32 +318,50 @@ public class JDBCSessionManager extends AbstractSessionManager
@Override
protected void complete()
{
- super.complete();
- try
+ synchronized (this)
{
- if (_dirty)
+ super.complete();
+ try
+ {
+ if (_dirty)
+ {
+ //The session attributes have changed, write to the db, ensuring
+ //http passivation/activation listeners called
+ willPassivate();
+ updateSession(this);
+ didActivate();
+ }
+ else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+ {
+ updateSessionAccessTime(this);
+ }
+ }
+ catch (Exception e)
{
- //The session attributes have changed, write to the db, ensuring
- //http passivation/activation listeners called
- willPassivate();
- updateSession(_data);
- didActivate();
+ LOG.warn("Problem persisting changed session data id="+getId(), e);
}
- else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000L))
+ finally
{
- updateSessionAccessTime(_data);
+ _dirty=false;
}
}
- catch (Exception e)
- {
- LOG.warn("Problem persisting changed session data id="+getId(), e);
- }
- finally
+ }
+
+ protected void save() throws Exception
+ {
+ synchronized (this)
{
- _dirty=false;
+ try
+ {
+ updateSession(this);
+ }
+ finally
+ {
+ _dirty = false;
+ }
}
}
-
+
@Override
protected void timeout() throws IllegalStateException
{
@@ -413,39 +369,15 @@ public class JDBCSessionManager extends AbstractSessionManager
LOG.debug("Timing out session id="+getClusterId());
super.timeout();
}
- }
-
-
-
-
- /**
- * ClassLoadingObjectInputStream
- *
- *
- */
- protected 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
+ public String toString ()
{
- try
- {
- return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
- }
- catch (ClassNotFoundException e)
- {
- return super.resolveClass(cl);
- }
+ return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
+ ",created="+getCreationTime()+",accessed="+getAccessed()+
+ ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
+ ",maxInterval="+getMaxInactiveInterval()+",lastSaved="+_lastSaved+",expiry="+_expiryTime;
}
}
@@ -500,7 +432,7 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
- * A session has been requested by it's id on this node.
+ * A session has been requested by its id on this node.
*
* Load the session by id AND context path from the database.
* Multiple contexts may share the same session id (due to dispatching)
@@ -518,12 +450,11 @@ public class JDBCSessionManager extends AbstractSessionManager
@Override
public Session getSession(String idInCluster)
{
- Session session = (Session)_sessions.get(idInCluster);
+ Session session = null;
+ Session memSession = (Session)_sessions.get(idInCluster);
synchronized (this)
{
- try
- {
//check if we need to reload the session -
//as an optimization, don't reload on every access
//to reduce the load on the database. This introduces a window of
@@ -532,85 +463,100 @@ public class JDBCSessionManager extends AbstractSessionManager
//re-migrated to this node. This should be an extremely rare occurrence,
//as load-balancers are generally well-behaved and consistently send
//sessions to the same node, changing only iff that node fails.
- SessionData data = null;
+ //Session data = null;
long now = System.currentTimeMillis();
if (LOG.isDebugEnabled())
{
- if (session==null)
+ if (memSession==null)
LOG.debug("getSession("+idInCluster+"): not in session map,"+
" now="+now+
- " lastSaved="+(session==null?0:session._data._lastSaved)+
+ " lastSaved="+(memSession==null?0:memSession._lastSaved)+
" interval="+(_saveIntervalSec * 1000L));
else
LOG.debug("getSession("+idInCluster+"): in session map, "+
" now="+now+
- " lastSaved="+(session==null?0:session._data._lastSaved)+
+ " lastSaved="+(memSession==null?0:memSession._lastSaved)+
" interval="+(_saveIntervalSec * 1000L)+
- " lastNode="+session._data.getLastNode()+
+ " lastNode="+memSession._lastNode+
" thisNode="+getSessionIdManager().getWorkerName()+
- " difference="+(now - session._data._lastSaved));
+ " difference="+(now - memSession._lastSaved));
}
- if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L)))
- {
- LOG.debug("getSession("+idInCluster+"): no session in session map or stale session. Reloading session data from db.");
- data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
- }
- else if ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L))
+ try
{
- LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
- data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ if (memSession==null)
+ {
+ LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
+ session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ }
+ else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+ {
+ LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
+ session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ }
+ else
+ {
+ LOG.debug("getSession("+idInCluster+"): session in session map");
+ session = memSession;
+ }
}
- else
+ catch (Exception e)
{
- LOG.debug("getSession("+idInCluster+"): session in session map");
- data = session._data;
+ LOG.warn("Unable to load session "+idInCluster, e);
+ return null;
}
- if (data != null)
+
+ //If we have a session
+ if (session != null)
{
- if (!data.getLastNode().equals(getSessionIdManager().getWorkerName()) || session==null)
+ //If the session was last used on a different node, or session doesn't exist on this node
+ if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
{
- //if the session has no expiry, or it is not already expired
- if (data._expiryTime <= 0 || data._expiryTime > now)
+ //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+ if (session._expiryTime <= 0 || session._expiryTime > now)
{
- if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): lastNode="+data.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
- data.setLastNode(getSessionIdManager().getWorkerName());
- //session last used on a different node, or we don't have it in memory
- session = new Session(now,data);
+ if (LOG.isDebugEnabled())
+ LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+
+ session.setLastNode(getSessionIdManager().getWorkerName());
_sessions.put(idInCluster, session);
- session.didActivate();
- //TODO is this the best way to do this? Or do this on the way out using
- //the _dirty flag?
- updateSessionNode(data);
+
+ //update in db
+ try
+ {
+ updateSessionNode(session);
+ session.didActivate();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+ return null;
+ }
}
else
{
- LOG.debug("getSession ({}): Session has expired", idInCluster);
-
+ 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;
}
}
else
- LOG.debug("getSession({}): Session not stale {}", idInCluster,session._data);
- //session in db shares same id, but is not for this context
+ LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
}
else
{
//No session in db with matching id and context path.
- session=null;
LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
}
return session;
- }
- catch (Exception e)
- {
- LOG.warn("Unable to load session from database", e);
- return null;
- }
}
}
+
/**
* Get the number of sessions.
@@ -737,7 +683,7 @@ public class JDBCSessionManager extends AbstractSessionManager
try
{
if (session != null)
- deleteSession(session._data);
+ deleteSession(session);
}
catch (Exception e)
{
@@ -768,9 +714,12 @@ public class JDBCSessionManager extends AbstractSessionManager
//then session data will be lost.
try
{
- session.willPassivate();
- storeSession(((JDBCSessionManager.Session)session)._data);
- session.didActivate();
+ synchronized (session)
+ {
+ session.willPassivate();
+ storeSession(((JDBCSessionManager.Session)session));
+ session.didActivate();
+ }
}
catch (Exception e)
{
@@ -804,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());
@@ -840,19 +789,20 @@ public class JDBCSessionManager extends AbstractSessionManager
*
* @param sessionIds
*/
- protected void expire (List<?> sessionIds)
+ protected Set<String> expire (Set<String> 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<String> successfullyExpiredIds = new HashSet<String>();
try
{
+ Iterator<?> itor = sessionIds.iterator();
while (itor.hasNext())
{
String sessionId = (String)itor.next();
@@ -860,46 +810,69 @@ public class JDBCSessionManager extends AbstractSessionManager
LOG.debug("Expiring session id "+sessionId);
Session session = (Session)_sessions.get(sessionId);
- if (session != null)
+
+ //if session is not in our memory, then fetch from db so we can call the usual listeners on it
+ if (session == null)
{
- session.timeout();
- itor.remove();
+ 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;
+ }
}
- else
+
+ if (session != null)
{
- if (LOG.isDebugEnabled())
- LOG.debug("Unrecognized session id="+sessionId);
+ session.timeout();
+ 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
* @return the session data that was loaded
* @throws Exception
*/
- protected SessionData loadSession (final String id, final String canonicalContextPath, final String vhost)
+ protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
throws Exception
{
- final AtomicReference<SessionData> _reference = new AtomicReference<SessionData>();
+ final AtomicReference<Session> _reference = new AtomicReference<Session>();
final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
Runnable load = new Runnable()
{
+ /**
+ * @see java.lang.Runnable#run()
+ */
@SuppressWarnings("unchecked")
public void run()
{
- SessionData data = null;
+ Session session = null;
Connection connection=null;
PreparedStatement statement = null;
try
@@ -908,29 +881,37 @@ public class JDBCSessionManager extends AbstractSessionManager
statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
ResultSet result = statement.executeQuery();
if (result.next())
- {
- data = new SessionData(id);
- data.setRowId(result.getString(_jdbcSessionIdMgr._sessionTableRowId));
- data.setCookieSet(result.getLong("cookieTime"));
- data.setLastAccessed(result.getLong("lastAccessTime"));
- data.setAccessed (result.getLong("accessTime"));
- data.setCreated(result.getLong("createTime"));
- data.setLastNode(result.getString("lastNode"));
- data.setLastSaved(result.getLong("lastSavedTime"));
- data.setExpiryTime(result.getLong("expiryTime"));
- data.setCanonicalContext(result.getString("contextPath"));
- data.setVirtualHost(result.getString("virtualHost"));
-
+ {
+ long maxInterval = result.getLong("maxInterval");
+ if (maxInterval == JDBCSessionIdManager.MAX_INTERVAL_NOT_SET)
+ {
+ maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
+ }
+ session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId),
+ result.getLong("createTime"),
+ result.getLong("accessTime"),
+ maxInterval);
+ session.setCookieSet(result.getLong("cookieTime"));
+ session.setLastAccessedTime(result.getLong("lastAccessTime"));
+ session.setLastNode(result.getString("lastNode"));
+ session.setLastSaved(result.getLong("lastSavedTime"));
+ session.setExpiryTime(result.getLong("expiryTime"));
+ session.setCanonicalContext(result.getString("contextPath"));
+ session.setVirtualHost(result.getString("virtualHost"));
+
InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
Object o = ois.readObject();
- data.setAttributeMap((Map<String,Object>)o);
+ session.addAttributes((Map<String,Object>)o);
ois.close();
if (LOG.isDebugEnabled())
- LOG.debug("LOADED session "+data);
+ LOG.debug("LOADED session "+session);
}
- _reference.set(data);
+ else
+ if (LOG.isDebugEnabled())
+ LOG.debug("Failed to load session "+id);
+ _reference.set(session);
}
catch (Exception e)
{
@@ -953,7 +934,12 @@ public class JDBCSessionManager extends AbstractSessionManager
_context.getContextHandler().handle(load);
if (_exception.get()!=null)
+ {
+ //if the session could not be restored, take its id out of the pool of currently-in-use
+ //session ids
+ _jdbcSessionIdMgr.removeSession(id);
throw _exception.get();
+ }
return _reference.get();
}
@@ -964,10 +950,10 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @throws Exception
*/
- protected void storeSession (SessionData data)
+ protected void storeSession (Session session)
throws Exception
{
- if (data==null)
+ if (session==null)
return;
//put into the database
@@ -975,38 +961,40 @@ public class JDBCSessionManager extends AbstractSessionManager
PreparedStatement statement = null;
try
{
- String rowId = calculateRowId(data);
+ String rowId = calculateRowId(session);
long now = System.currentTimeMillis();
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
statement.setString(1, rowId); //rowId
- statement.setString(2, data.getId()); //session id
- statement.setString(3, data.getCanonicalContext()); //context path
- statement.setString(4, data.getVirtualHost()); //first vhost
+ statement.setString(2, session.getId()); //session id
+ statement.setString(3, session.getCanonicalContext()); //context path
+ statement.setString(4, session.getVirtualHost()); //first vhost
statement.setString(5, getSessionIdManager().getWorkerName());//my node id
- statement.setLong(6, data.getAccessed());//accessTime
- statement.setLong(7, data.getLastAccessed()); //lastAccessTime
- statement.setLong(8, data.getCreated()); //time created
- statement.setLong(9, data.getCookieSet());//time cookie was set
+ statement.setLong(6, session.getAccessed());//accessTime
+ statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
+ statement.setLong(8, session.getCreationTime()); //time created
+ statement.setLong(9, session.getCookieSet());//time cookie was set
statement.setLong(10, now); //last saved time
- statement.setLong(11, data.getExpiryTime());
+ statement.setLong(11, session.getExpiryTime());
+ statement.setLong(12, session.getMaxInactiveInterval());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(data.getAttributeMap());
+ oos.writeObject(session.getAttributeMap());
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
- statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
+ statement.setBinaryStream(13, bais, bytes.length);//attribute map as blob
+
statement.executeUpdate();
- data.setRowId(rowId); //set it on the in-memory data as well as in db
- data.setLastSaved(now);
+ session.setRowId(rowId); //set it on the in-memory data as well as in db
+ session.setLastSaved(now);
if (LOG.isDebugEnabled())
- LOG.debug("Stored session "+data);
+ LOG.debug("Stored session "+session);
}
finally
{
@@ -1019,10 +1007,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Update data on an existing persisted session.
*
- * @param data
+ * @param data the session
* @throws Exception
*/
- protected void updateSession (SessionData data)
+ protected void updateSession (Session data)
throws Exception
{
if (data==null)
@@ -1038,9 +1026,10 @@ public class JDBCSessionManager extends AbstractSessionManager
statement.setString(1, data.getId());
statement.setString(2, getSessionIdManager().getWorkerName());//my node id
statement.setLong(3, data.getAccessed());//accessTime
- statement.setLong(4, data.getLastAccessed()); //lastAccessTime
+ statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
statement.setLong(5, now); //last saved time
statement.setLong(6, data.getExpiryTime());
+ statement.setLong(7, data.getMaxInactiveInterval());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
@@ -1048,8 +1037,8 @@ public class JDBCSessionManager extends AbstractSessionManager
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
- statement.setBinaryStream(7, bais, bytes.length);//attribute map as blob
- statement.setString(8, data.getRowId()); //rowId
+ statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob
+ statement.setString(9, data.getRowId()); //rowId
statement.executeUpdate();
data.setLastSaved(now);
@@ -1067,10 +1056,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Update the node on which the session was last seen to be my node.
*
- * @param data
+ * @param data the session
* @throws Exception
*/
- protected void updateSessionNode (SessionData data)
+ protected void updateSessionNode (Session data)
throws Exception
{
String nodeId = getSessionIdManager().getWorkerName();
@@ -1097,10 +1086,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Persist the time the session was last accessed.
*
- * @param data
+ * @param data the session
* @throws Exception
*/
- private void updateSessionAccessTime (SessionData data)
+ private void updateSessionAccessTime (Session data)
throws Exception
{
Connection connection = getConnection();
@@ -1112,10 +1101,12 @@ public class JDBCSessionManager extends AbstractSessionManager
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
statement.setString(1, getSessionIdManager().getWorkerName());
statement.setLong(2, data.getAccessed());
- statement.setLong(3, data.getLastAccessed());
+ statement.setLong(3, data.getLastAccessedTime());
statement.setLong(4, now);
statement.setLong(5, data.getExpiryTime());
- statement.setString(6, data.getRowId());
+ statement.setLong(6, data.getMaxInactiveInterval());
+ statement.setString(7, data.getRowId());
+
statement.executeUpdate();
data.setLastSaved(now);
statement.close();
@@ -1139,7 +1130,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @throws Exception
*/
- protected void deleteSession (SessionData data)
+ protected void deleteSession (Session data)
throws Exception
{
Connection connection = getConnection();
@@ -1180,7 +1171,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @return
*/
- private String calculateRowId (SessionData data)
+ private String calculateRowId (Session data)
{
String rowId = canonicalize(_context.getContextPath());
rowId = rowId + "_" + getVirtualHost(_context);
@@ -1195,7 +1186,7 @@ public class JDBCSessionManager extends AbstractSessionManager
*
* @return 0.0.0.0 if no virtual host is defined
*/
- private String getVirtualHost (ContextHandler.Context context)
+ private static String getVirtualHost (ContextHandler.Context context)
{
String vhost = "0.0.0.0";
@@ -1215,7 +1206,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param path
* @return
*/
- private String canonicalize (String path)
+ private static String canonicalize (String path)
{
if (path==null)
return "";
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 e5d9e104a6..00358ca875 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 0000000000..4d3714b997
--- /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("<h1>Test</h1>");
+ }
+
+ 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 662f1875ce..815592d874 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<Object[]> 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 de5fbb7313..ca1c4ee612 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 4867016204..9e290ab2ec 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();
-
- 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<String> qualities=fields.getValues("Q",", \t");
- List<String> 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);
- }
+ 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<String> qualities=fields.getValues("Q",", \t");
+ List<String> 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 0000000000..596f9ccc2b
--- /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 1698166bc2..1de5bc71b5 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/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index 819563ea33..ef919630bc 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -421,6 +421,7 @@ public class StatisticsHandlerTest
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
+ event.getAsyncContext().complete();
}
@Override
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 12825dbf59..11ad5a90f5 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-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
index 0d67bb3f75..f95a58dff8 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -434,7 +434,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (resource!=null && resource.exists() && !resource.isDirectory())
{
// Tell caches that response may vary by accept-encoding
- response.setHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+ response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
// Does the client accept gzip?
String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
index 3f41d53271..e69910480a 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
@@ -97,7 +97,19 @@ public class FilterHolder extends Holder<Filter>
super.stop();
throw new IllegalStateException(msg);
}
+ }
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void initialize() throws Exception
+ {
+ super.initialize();
+
if (_filter==null)
{
try
@@ -123,6 +135,7 @@ public class FilterHolder extends Holder<Filter>
_filter.init(_config);
}
+
/* ------------------------------------------------------------ */
@Override
public void doStop()
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
index f67b635bf1..0b7a1fb8f0 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
@@ -82,6 +82,19 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
{
return _extInstance;
}
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Do any setup necessary after starting
+ * @throws Exception
+ */
+ public void initialize()
+ throws Exception
+ {
+ if (!isStarted())
+ throw new IllegalStateException("Not started: "+this);
+ }
/* ------------------------------------------------------------ */
@SuppressWarnings("unchecked")
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index 34ea485f1b..9baefac40a 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -518,12 +518,12 @@ public class ServletHandler extends ScopedHandler
{
UnavailableException ue = (UnavailableException)th;
if (ue.isPermanent())
- response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
else
- response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
+ response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
else
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
LOG.debug("Response already committed for handling "+th);
@@ -542,7 +542,7 @@ public class ServletHandler extends ScopedHandler
{
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
LOG.debug("Response already committed for handling ",e);
@@ -741,6 +741,7 @@ public class ServletHandler extends ScopedHandler
try
{
h.start();
+ h.initialize();
}
catch (Exception e)
{
@@ -783,7 +784,7 @@ public class ServletHandler extends ScopedHandler
public ServletHolder addServletWithMapping (String className,String pathSpec)
{
ServletHolder holder = newServletHolder(null);
- holder.setName(className+"-"+_servlets.length);
+ holder.setName(className+"-"+(_servlets==null?0:_servlets.length));
holder.setClassName(className);
addServletWithMapping(holder,pathSpec);
return holder;
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 8ff369ef0e..6de3b7e48b 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -280,7 +280,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
_enabled = enabled;
}
-
+
/* ------------------------------------------------------------ */
public void doStart()
throws Exception
@@ -319,7 +319,17 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
_servlet = new SingleThreadedWrapper();
-
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void initialize ()
+ throws Exception
+ {
+ super.initialize();
+
if (_extInstance || _initOnStartup)
{
try
@@ -335,7 +345,8 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
}
}
}
-
+
+
/* ------------------------------------------------------------ */
public void doStop()
throws Exception
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
new file mode 100644
index 0000000000..d5ad4ee452
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -0,0 +1,654 @@
+//
+// ========================================================================
+// 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.IO;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class AsyncServletTest
+{
+ protected AsyncServlet _servlet=new AsyncServlet();
+ protected int _port;
+
+ protected Server _server = new Server();
+ protected ServletHandler _servletHandler;
+ protected ServerConnector _connector;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ _connector = new ServerConnector(_server);
+ _server.setConnectors(new Connector[]{ _connector });
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
+ context.setContextPath("/ctx");
+ _server.setHandler(context);
+ _servletHandler=context.getServletHandler();
+ ServletHolder holder=new ServletHolder(_servlet);
+ holder.setAsyncSupported(true);
+ _servletHandler.addServletWithMapping(holder,"/path/*");
+ _server.start();
+ _port=_connector.getLocalPort();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ _server.stop();
+ }
+
+ @Test
+ public void testNormal() throws Exception
+ {
+ String response=process(null,null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n",response);
+ assertContains("NORMAL",response);
+ assertNotContains("history: onTimeout",response);
+ assertNotContains("history: onComplete",response);
+ }
+
+ @Test
+ public void testSleep() throws Exception
+ {
+ String response=process("sleep=200",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n",response);
+ assertContains("SLEPT",response);
+ assertNotContains("history: onTimeout",response);
+ assertNotContains("history: onComplete",response);
+ }
+
+ @Test
+ public void testSuspend() throws Exception
+ {
+ String response=process("suspend=200",null);
+ assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: onTimeout\r\n"+
+ "history: ERROR\r\n"+
+ "history: !initial\r\n"+
+ "history: onComplete\r\n",response);
+
+ assertContains("ERROR: /ctx/path/info",response);
+ }
+
+ @Test
+ public void testSuspendOnTimeoutDispatch() throws Exception
+ {
+ String response=process("suspend=200&timeout=dispatch",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: onTimeout\r\n"+
+ "history: dispatch\r\n"+
+ "history: ASYNC\r\n"+
+ "history: !initial\r\n"+
+ "history: onComplete\r\n",response);
+
+ assertContains("DISPATCHED",response);
+ }
+
+ @Test
+ public void testSuspendOnTimeoutComplete() throws Exception
+ {
+ String response=process("suspend=200&timeout=complete",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: onTimeout\r\n"+
+ "history: complete\r\n"+
+ "history: onComplete\r\n",response);
+
+ assertContains("COMPLETED",response);
+ }
+
+ @Test
+ public void testSuspendWaitResume() throws Exception
+ {
+ String response=process("suspend=200&resume=10",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: resume\r\n"+
+ "history: ASYNC\r\n"+
+ "history: !initial\r\n"+
+ "history: onComplete\r\n",response);
+ assertNotContains("history: onTimeout",response);
+ }
+
+ @Test
+ public void testSuspendResume() throws Exception
+ {
+ String response=process("suspend=200&resume=0",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: resume\r\n"+
+ "history: ASYNC\r\n"+
+ "history: !initial\r\n"+
+ "history: onComplete\r\n",response);
+ assertContains("history: onComplete",response);
+ }
+
+ @Test
+ public void testSuspendWaitComplete() throws Exception
+ {
+ String response=process("suspend=200&complete=50",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: complete\r\n"+
+ "history: onComplete\r\n",response);
+ assertContains("COMPLETED",response);
+ assertNotContains("history: onTimeout",response);
+ assertNotContains("history: !initial",response);
+ }
+
+ @Test
+ public void testSuspendComplete() throws Exception
+ {
+ String response=process("suspend=200&complete=0",null);
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: complete\r\n"+
+ "history: onComplete\r\n",response);
+ assertContains("COMPLETED",response);
+ assertNotContains("history: onTimeout",response);
+ assertNotContains("history: !initial",response);
+ }