diff --git a/.gitignore b/.gitignore index 56b34575555..cd084872657 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,36 @@ -target/ +# eclipse .classpath .project .settings + +# maven +target/ */src/main/java/META-INF/ -.pmd + +# common junk *.log *.swp *.diff *.patch + +# intellij *.iml *.ipr *.iws .idea/ + +# Mac filesystem dust +.DS_Store + +# pmd +.pmdruleset +.pmd + +# netbeans +/nbproject + +# vim +.*.sw[a-p] + +# merge tooling +*.orig diff --git a/VERSION.txt b/VERSION.txt index 1d9a511edb6..eb6e1de36f0 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,4 +1,152 @@ -jetty-7.5.0.v20110901 01 September 2011 +jetty-7.6.0-SNAPSHOT + +jetty-7.6.0.RC2 - 22 December 2011 + + 364638 HttpParser closes if data received while seeking EOF. Tests fixed to + cope + + 364921 Made test less time sensitive for ssl + + 364936 use Resource for opening URL streams + + 365267 NullPointerException in bad Address + + 365375 ResourceHandler should be a HandlerWrapper + + 365750 Support WebSocket over SSL, aka wss:// + + 365932 Produce jetty-websocket aggregate jar for android use + + 365947 Set headers for Auth failure and retry in http-spi + + 366316 Superfluous printStackTrace on 404 + + 366342 Dont persist DosFilter trackers in http session + + 366730 pass the time idle to onIdleExpire + + 367048 test harness for guard on suspended requests + + 367175 SSL 100% CPU spin in case of blocked write and RST. + + 367219 WebSocketClient.open() fails when URI uses default ports. + + JETTY-1460 suppress PrintWriter exceptions + + JETTY-1463 websocket D0 parser should return progress even if no fill done + + JETTY-1465 NPE in ContextHandler.toString + +jetty-7.6.0.RC1 - 04 December 2011 + + 352565 cookie httponly flag ignored + + 353285 ServletSecurity annotation ignored + + 357163 jetty 8 ought to proxy jetty8 javadocs + + 357209 JSP tag listeners not called + + 360051 SocketConnectionTest.testServerClosedConnection is excluded. + + 361135 Allow session cookies to NEVER be marked as secure, even on HTTPS + requests. + + 362249 update shell scripts to jetty8 + + 363878 Add ecj compiler to jetty-8 for jsp + + 364283 can't parse the servlet multipart-config for the web.xml + + 364430 Support web.xml enabled state for servlets + + 365370 ServletHandler can fall through to nested handler + +jetty-7.6.0.RC0 - 29 November 2011 + + Refactored NIO layer for better half close handling + + 349110 fixed bypass chunk handling + + 360546 handle set count exceeding max integer + + 362111 StdErrLog.isDebugEnabled() returns true too often + + 362113 Improve Test Coverage of org.eclipse.jetty.util.log classes + + 362407 setTrustStore(Resource) -> setTrustStoreResource(R) + + 362447 add setMaxNonceAge() to DigestAuthenticator + + 362468 NPE at line org.eclipse.jetty.io.BufferUtil.putHexInt + + 362614 NPE in accepting connection + + 362626 IllegalStateException thrown when SslContextFactory preconfigured + with SSLContext + + 362696 expand virtual host configuration options to ContextHandler and add + associated test case for new behavior + + 362742 improved UTF8 exception reason + + 363124 improved websocket close handling + + 363381 Throw IllegalStateException if Request uri is null on getServerName + + 363408 GzipFilter should not attempt to compress HTTP status 204 + + 363488 ShutdownHandler use stopper thread + + 363718 Setting java.rmi.server.hostname in jetty-jmx.xml + + 363757 partial fix + + 363785 StdErrLog must use system-dependent EOL. + + 363943 ignore null attribute values + + 363993 EOFException parsing HEAD response in HttpTester + + 364638 SCEP does idle timestamp checking. New setCheckForIdle method + controls onIdleExpired callback. 364921 a second onIdleExpired callback will + result in close rather than a shutdown output. + + 364657 Support HTTP only cookies from standard API + + JETTY-1442 add _hostHeader setter for ProxyRule + +jetty-7.5.4.v20111024 - 24 October 2011 + + 358263 JDBCSessionIdManager add setDatasource(DataSource) method + + 358649 Replace existing StdErrLog system properties for DEBUG/IGNORED with + LEVEL instead. + + 360836 Accept parameters with bad UTF-8. Use replacement character + + 360912 CrossOriginFilter does not send Access-Control-Allow-Origin on + responses. 355103 Make allowCredentials default to true in + CrossOriginFilter. + + 360938 Connections closed after a while. + + 361319 Log initialization does not catch correct exceptions on all jvms + + 361325 359292 Allow KeyStore to be set + + 361456 release timer task on connection failed + + 361655 ExecutorThreadPool.isLowOnThreads() returns wrong value. + + JETTY-1444 start threadpool before selector manager + +jetty-7.5.3.v20111011 - 11 October 2011 + + 348978 migrate jetty-http-spi + + 358649 StdErrLog system properties for package/class logging LEVEL. + +jetty-7.5.2.v20111006 - 06 October 2011 + + 336443 check nonce count is increasing + + 342161 ScannerTest fails intermittently on Mac OS X + + 346419 testing HttpClient FDs + + 353267 Request._parameters initialization bug + + 353509 jetty-client unit tests are running too long + + 353627 Basic Auth checks that Basic method has been send + + 356144 Allow SelectorManager thread priority to be set + + 356274 Start SSL socket factory in call to open() + + 357178 websockets draft 14 support + + 357188 Send content buffer directly + + 357216 Logging via Log4J does not expand braces in format strings + + 357240 more half close refinements + + 357338 remove debug + + 357672 resolve issue with serializing pojos with mongodb session manager, + thanks to john simone for the discovery and fix + + 357959 Include javadoc in distribution + + 358027 NullPointerException in ResourceHandler with jetty-stylesheet.css + + 358035 idle time only active if > 0 + + 358147 Add catch for UnknownHostException to fix leaky file descriptor in + client + + 358164 Dispatch from servlet to handler + + 358263 add method for osgi users to register a driver as Class.forName does + not work for them + + 358649 StdErrLog system properties for package/class logging LEVEL. + + 358674 Still allows sslv3 for now + + 358687 Updated jsp does not scan for system tlds Fixed pattern. + + 358784 JSP broken on Java 1.5 + + 358925 bit more javadoc on usage + + 358959 File descriptor leak with UnresolvedAddressException + + 359309 adjust previous test for servletPath to include pathInfo + + 359673 updated websocket version handling + + 359675 Principal != String, fix for issue in property file login manager + + 360051 SocketConnectionTest.testServerClosedConnection is excluded. + + 360066 jsps referenced in web.xml elements do not compile + + JETTY-1130 Access Sessions from HashSessionIdManager + + JETTY-1277 Fixed sendRedirect encoding of relative locations + + JETTY-1322 idle sweeper checks for closed endp + + JETTY-1377 extra logging for busy selector + + JETTY-1378 new sys property for the latest jsp-impl to force the use of the + JDTCompiler when running in OSGi. + + JETTY-1414 applied to PropertyUserStore + + JETTY-1415 Start/Stop Server and Client only once in test, code format + + JETTY-1420 Set Host header for new request in RedirectListener + + JETTY-1421 Implement RedirectListener.onException,onConnectionFailed + + JETTY-1423 force connection to be closed returned + + JETTY-1430 local JNDI contexts don't carry environment + + JETTY-1434 Add a jsp that exercises jstl. + + JETTY-1439 space in directory installation path causes classloader problem + +jetty-7.5.1.v20110908 - 08 September 2011 + + 350634 Added Resource.newResource(File) + + 356190 fix monodb tests for changed test api + + 356428 removed timed waits from test + + 356693 reduce visibility to webapp of websocket implementations + + 356695 jetty server jars are provided for websockets + + 356726 Instead of the sessionDestroyed called sessionCreated after + invalidate session + + 356751 Add null protection to ServletContextHandler.doStop + + 356823 correctly decode close codes. Send not utf-8 close code. + + 357058 Acceptor thread blocking + +jetty-7.5.0.v20110901 - 01 September 2011 + + 356421 Upgraded websocket to draft 13 support + 353073 better warnings jetty-7.5.0.RC2 - 30 August 2011 @@ -57,6 +205,12 @@ jetty-7.5.0.RC0 - 15 August 2011 + 354397 RewriteRegexRule handles special characters in regex group + 354466 Typo in example config of jetty-plus.xml +jetty-7.4.5.v20110725 - 25 July 2011 + + 347484 / - > ${/} in some paths in grant codebases + + 352133 resolve some 1.5isms + + 352421 HttpURI paths beginning with '.' + + 352786 GzipFilter fails to pass parameters to GzipResponseWrapper + jetty-7.4.4.v20110707 - 07 July 2011 + 308851 Converted all jetty-client module tests to JUnit 4 + 345268 JDBCSessionManager does not work with maxInactiveInterval = -1 diff --git a/example-jetty-embedded/pom.xml b/example-jetty-embedded/pom.xml index db238297cd6..3eb95b0ea27 100644 --- a/example-jetty-embedded/pom.xml +++ b/example-jetty-embedded/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 example-jetty-embedded @@ -28,6 +28,11 @@ jetty-servlets ${project.version} + + org.eclipse.jetty + jetty-rewrite + ${project.version} + org.eclipse.jetty jetty-deploy @@ -42,6 +47,11 @@ org.eclipse.jetty jetty-ajp ${project.version} + + + org.eclipse.jetty.toolchain + jetty-test-helper + test org.eclipse.jetty diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java index ce7329c2245..778c9b0b234 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java @@ -15,11 +15,9 @@ package org.eclipse.jetty.embedded; import java.lang.management.ManagementFactory; -import org.eclipse.jetty.ajp.Ajp13SocketConnector; import org.eclipse.jetty.deploy.DeploymentManager; import org.eclipse.jetty.deploy.providers.ContextProvider; import org.eclipse.jetty.deploy.providers.WebAppProvider; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.Connector; @@ -31,9 +29,12 @@ import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.server.nio.BlockingChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; public class LikeJettyXml @@ -51,12 +52,12 @@ public class LikeJettyXml MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); server.getContainer().addEventListener(mbContainer); server.addBean(mbContainer); - mbContainer.addBean(Log.getLog()); + mbContainer.addBean(Log.getRootLogger()); // Setup Threadpool QueuedThreadPool threadPool = new QueuedThreadPool(); - threadPool.setMaxThreads(100); + threadPool.setMaxThreads(500); server.setThreadPool(threadPool); // Setup Connectors @@ -64,15 +65,22 @@ public class LikeJettyXml connector.setPort(8080); connector.setMaxIdleTime(30000); connector.setConfidentialPort(8443); - connector.setStatsOn(true); + connector.setStatsOn(false); server.setConnectors(new Connector[] { connector }); + + BlockingChannelConnector bConnector = new BlockingChannelConnector(); + bConnector.setPort(8888); + bConnector.setMaxIdleTime(30000); + bConnector.setConfidentialPort(8443); + bConnector.setAcceptors(1); + server.addConnector(bConnector); SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector(); ssl_connector.setPort(8443); SslContextFactory cf = ssl_connector.getSslContextFactory(); - cf.setKeyStore(jetty_home + "/etc/keystore"); + cf.setKeyStorePath(jetty_home + "/etc/keystore"); cf.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); cf.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); cf.setTrustStore(jetty_home + "/etc/keystore"); @@ -87,13 +95,25 @@ public class LikeJettyXml "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" }); - ssl_connector.setStatsOn(true); + ssl_connector.setStatsOn(false); server.addConnector(ssl_connector); + ssl_connector.open(); + + SslSocketConnector ssl2_connector = new SslSocketConnector(cf); + ssl2_connector.setPort(8444); + ssl2_connector.setStatsOn(false); + server.addConnector(ssl2_connector); + ssl2_connector.open(); + + /* + Ajp13SocketConnector ajp = new Ajp13SocketConnector(); ajp.setPort(8009); server.addConnector(ajp); + */ + HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); RequestLogHandler requestLogHandler = new RequestLogHandler(); @@ -136,10 +156,9 @@ public class LikeJettyXml server.setStopAtShutdown(true); server.setSendServerVersion(true); - + server.start(); server.join(); } - } diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java index 8e533d8296a..b60b8c1483c 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java @@ -13,11 +13,11 @@ package org.eclipse.jetty.embedded; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; /* ------------------------------------------------------------ */ @@ -47,7 +47,7 @@ public class ManyConnectors System.setProperty("jetty.home",jetty_home); ssl_connector.setPort(8443); SslContextFactory cf = ssl_connector.getSslContextFactory(); - cf.setKeyStore(jetty_home + "/etc/keystore"); + cf.setKeyStorePath(jetty_home + "/etc/keystore"); cf.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); cf.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java index 7224824cb3e..d02754041b0 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java @@ -17,19 +17,19 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.security.Constraint; public class SecuredHelloHandler { public static void main(String[] args) throws Exception { - Server server = new Server(0); + Server server = new Server(8080); LoginService loginService = new HashLoginService("MyRealm","src/test/resources/realm.properties"); server.addBean(loginService); diff --git a/jetty-aggregate/jetty-all-server/pom.xml b/jetty-aggregate/jetty-all-server/pom.xml index be14257209a..072ff23321e 100644 --- a/jetty-aggregate/jetty-all-server/pom.xml +++ b/jetty-aggregate/jetty-all-server/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-all-server Jetty :: Aggregate :: All Server @@ -25,7 +24,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/** **/MANIFEST.MF,javax/** ${project.build.directory}/classes false @@ -146,12 +145,6 @@ ${project.version} provided - - org.eclipse.jetty - jetty-jsp-2.1 - ${project.version} - provided - org.eclipse.jetty jetty-plus diff --git a/jetty-aggregate/jetty-all/pom.xml b/jetty-aggregate/jetty-all/pom.xml index 2ae77b9e571..3a6eee3ac51 100644 --- a/jetty-aggregate/jetty-all/pom.xml +++ b/jetty-aggregate/jetty-all/pom.xml @@ -2,13 +2,11 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-all Jetty :: Aggregate :: All core Jetty - ${project.build.directory}/sources @@ -22,7 +20,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/* **/MANIFEST.MF,javax/** ${project.build.directory}/classes false @@ -75,6 +73,19 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + javadoc-jar + compile + + jar + + + + @@ -118,12 +129,6 @@ ${project.version} provided - - org.eclipse.jetty - jetty-jsp-2.1 - ${project.version} - provided - org.eclipse.jetty jetty-plus diff --git a/jetty-aggregate/jetty-client/pom.xml b/jetty-aggregate/jetty-client/pom.xml index cd94450a439..e30ee5b160a 100644 --- a/jetty-aggregate/jetty-client/pom.xml +++ b/jetty-aggregate/jetty-client/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-client Jetty :: Aggregate :: HTTP Client diff --git a/jetty-aggregate/jetty-plus/pom.xml b/jetty-aggregate/jetty-plus/pom.xml index b1f64916343..ff9bf1f74c9 100644 --- a/jetty-aggregate/jetty-plus/pom.xml +++ b/jetty-aggregate/jetty-plus/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-plus Jetty :: Aggregate :: Plus Server diff --git a/jetty-aggregate/jetty-server/pom.xml b/jetty-aggregate/jetty-server/pom.xml index 4c7c25d21ff..8d3cdddcb2f 100644 --- a/jetty-aggregate/jetty-server/pom.xml +++ b/jetty-aggregate/jetty-server/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-server Jetty :: Aggregate :: HTTP Server diff --git a/jetty-aggregate/jetty-servlet/pom.xml b/jetty-aggregate/jetty-servlet/pom.xml index 3af2566d56c..2e4f9bd47ee 100644 --- a/jetty-aggregate/jetty-servlet/pom.xml +++ b/jetty-aggregate/jetty-servlet/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-servlet Jetty :: Aggregate :: Servlet Server diff --git a/jetty-aggregate/jetty-webapp/pom.xml b/jetty-aggregate/jetty-webapp/pom.xml index 3fc14df227c..47090b07d8f 100644 --- a/jetty-aggregate/jetty-webapp/pom.xml +++ b/jetty-aggregate/jetty-webapp/pom.xml @@ -2,10 +2,9 @@ org.eclipse.jetty.aggregate jetty-aggregate-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - org.eclipse.jetty.aggregate jetty-webapp Jetty :: Aggregate :: WebApp Server @@ -22,7 +21,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/** **/MANIFEST.MF,javax/** ${project.build.directory}/classes false @@ -96,11 +95,5 @@ servlet-api compile - - org.eclipse.jetty - jetty-jsp-2.1 - ${project.version} - provided - diff --git a/jetty-aggregate/jetty-websocket/pom.xml b/jetty-aggregate/jetty-websocket/pom.xml new file mode 100644 index 00000000000..c650ef55103 --- /dev/null +++ b/jetty-aggregate/jetty-websocket/pom.xml @@ -0,0 +1,88 @@ + + + org.eclipse.jetty.aggregate + jetty-aggregate-project + 7.6.0-SNAPSHOT + + 4.0.0 + jetty-websocket + Jetty :: Aggregate :: Websocket + + + ${project.build.directory}/sources + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-dependencies + + unpack-dependencies + + + META-INF/**,org/eclipse/** + **/MANIFEST.MF,javax/**,about.html + ${project.build.directory}/classes + false + true + + + + unpack-source + generate-sources + + unpack-dependencies + + + sources + **/* + META-INF/** + org.eclipse.jetty + ${project.build.directory}/sources + true + true + + + + + + org.apache.maven.plugins + + maven-jar-plugin + + + package + package + + jar + + + + + + + development + http://eclipse.org/jetty + ${user.name} + org.eclipse.jetty + http://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk/NOTICE.txt + Jetty HTTP Server + + + + + + + + + + + + org.eclipse.jetty + jetty-websocket + ${project.version} + provided + + + diff --git a/jetty-aggregate/pom.xml b/jetty-aggregate/pom.xml index 8c98e9064b6..7de1ac8d379 100644 --- a/jetty-aggregate/pom.xml +++ b/jetty-aggregate/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT org.eclipse.jetty.aggregate jetty-aggregate-project @@ -35,6 +35,7 @@ jetty-client jetty-servlet jetty-webapp + jetty-websocket jetty-plus jetty-all-server jetty-all diff --git a/jetty-ajp/pom.xml b/jetty-ajp/pom.xml index e2eceeb999a..c02155bea81 100644 --- a/jetty-ajp/pom.xml +++ b/jetty-ajp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-ajp diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java index 1afc64facfb..f29159b870a 100644 --- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java @@ -216,7 +216,10 @@ public class Ajp13Connection extends BlockingHttpConnection public void parsedRequestAttribute(String key, Buffer value) throws IOException { - _request.setAttribute(key, value.toString()); + if (value==null) + _request.removeAttribute(key); + else + _request.setAttribute(key,value.toString()); } public void parsedRequestAttribute(String key, int value) throws IOException diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java index 488af7401a3..77d17e6db04 100644 --- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java @@ -44,38 +44,38 @@ public class Ajp13Generator extends AbstractGenerator static { byte[] xA001 = - { (byte) 0xA0, (byte) 0x01 }; + { (byte)0xA0, (byte)0x01 }; byte[] xA002 = - { (byte) 0xA0, (byte) 0x02 }; + { (byte)0xA0, (byte)0x02 }; byte[] xA003 = - { (byte) 0xA0, (byte) 0x03 }; + { (byte)0xA0, (byte)0x03 }; byte[] xA004 = - { (byte) 0xA0, (byte) 0x04 }; + { (byte)0xA0, (byte)0x04 }; byte[] xA005 = - { (byte) 0xA0, (byte) 0x05 }; + { (byte)0xA0, (byte)0x05 }; byte[] xA006 = - { (byte) 0xA0, (byte) 0x06 }; + { (byte)0xA0, (byte)0x06 }; byte[] xA007 = - { (byte) 0xA0, (byte) 0x07 }; + { (byte)0xA0, (byte)0x07 }; byte[] xA008 = - { (byte) 0xA0, (byte) 0x08 }; + { (byte)0xA0, (byte)0x08 }; byte[] xA009 = - { (byte) 0xA0, (byte) 0x09 }; + { (byte)0xA0, (byte)0x09 }; byte[] xA00A = - { (byte) 0xA0, (byte) 0x0A }; + { (byte)0xA0, (byte)0x0A }; byte[] xA00B = - { (byte) 0xA0, (byte) 0x0B }; - __headerHash.put("Content-Type", xA001); - __headerHash.put("Content-Language", xA002); - __headerHash.put("Content-Length", xA003); - __headerHash.put("Date", xA004); - __headerHash.put("Last-Modified", xA005); - __headerHash.put("Location", xA006); - __headerHash.put("Set-Cookie", xA007); - __headerHash.put("Set-Cookie2", xA008); - __headerHash.put("Servlet-Engine", xA009); - __headerHash.put("Status", xA00A); - __headerHash.put("WWW-Authenticate", xA00B); + { (byte)0xA0, (byte)0x0B }; + __headerHash.put("Content-Type",xA001); + __headerHash.put("Content-Language",xA002); + __headerHash.put("Content-Length",xA003); + __headerHash.put("Date",xA004); + __headerHash.put("Last-Modified",xA005); + __headerHash.put("Location",xA006); + __headerHash.put("Set-Cookie",xA007); + __headerHash.put("Set-Cookie2",xA008); + __headerHash.put("Servlet-Engine",xA009); + __headerHash.put("Status",xA00A); + __headerHash.put("WWW-Authenticate",xA00B); } @@ -83,7 +83,7 @@ public class Ajp13Generator extends AbstractGenerator // 0, 1 ajp int 1 packet length // 9 CPONG response Code private static final byte[] AJP13_CPONG_RESPONSE = - { 'A', 'B', 0, 1, 9}; + { 'A', 'B', 0, 1, 9 }; private static final byte[] AJP13_END_RESPONSE = { 'A', 'B', 0, 2, 5, 1 }; @@ -114,7 +114,7 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ public Ajp13Generator(Buffers buffers, EndPoint io) { - super(buffers, io); + super(buffers,io); } /* ------------------------------------------------------------ */ @@ -130,19 +130,18 @@ public class Ajp13Generator extends AbstractGenerator { return true; } + /* ------------------------------------------------------------ */ @Override - public void reset(boolean returnBuffers) + public void reset() { - super.reset(returnBuffers); + super.reset(); _needEOC = false; _needMore = false; _expectMore = false; _bufferPrepared = false; - _last=false; - - + _last = false; _state = STATE_HEADER; @@ -159,11 +158,9 @@ public class Ajp13Generator extends AbstractGenerator _noContent = false; _persistent = true; - - - _header = null; // Buffer for HTTP header (and maybe small _content) - _buffer = null; // Buffer for copy of passed _content - _content = null; // Buffer passed to addContent + _header = null; // Buffer for HTTP header (and maybe small _content) + _buffer = null; // Buffer for copy of passed _content + _content = null; // Buffer passed to addContent } @@ -175,13 +172,13 @@ public class Ajp13Generator extends AbstractGenerator { initContent(); } - catch(IOException e) + catch (IOException e) { throw new RuntimeException(e); } - return super.getContentBufferSize()-7; + return super.getContentBufferSize() - 7; } - + /* ------------------------------------------------------------ */ @Override public void increaseContentBufferSize(int contentBufferSize) @@ -196,11 +193,9 @@ public class Ajp13Generator extends AbstractGenerator * @param content * @param last * @throws IllegalArgumentException - * if content is - * {@link Buffer#isImmutable immutable}. + * if content is {@link Buffer#isImmutable immutable}. * @throws IllegalStateException - * If the request is not expecting any more content, or if the - * buffers are full and cannot be flushed. + * If the request is not expecting any more content, or if the buffers are full and cannot be flushed. * @throws IOException * if there is a problem flushing the buffers. */ @@ -217,13 +212,13 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) { - LOG.debug("Ignoring extra content {}", content); + LOG.debug("Ignoring extra content {}",content); content.clear(); return; } _last = last; - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return; @@ -289,8 +284,7 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) throw new IllegalStateException("Closed"); - - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return false; @@ -322,8 +316,7 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ /** - * Prepare buffer for unchecked writes. Prepare the generator buffer to - * receive unchecked writes + * Prepare buffer for unchecked writes. Prepare the generator buffer to receive unchecked writes * * @return the available space in the buffer. * @throws IOException @@ -337,8 +330,7 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) throw new IllegalStateException("Closed"); - - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return -1; @@ -377,8 +369,8 @@ public class Ajp13Generator extends AbstractGenerator _last = _last | allContentAdded; boolean has_server = false; - if (_persistent==null) - _persistent=(_version > HttpVersions.HTTP_1_0_ORDINAL); + if (_persistent == null) + _persistent = (_version > HttpVersions.HTTP_1_0_ORDINAL); // get a header buffer if (_header == null) @@ -390,13 +382,13 @@ public class Ajp13Generator extends AbstractGenerator try { // start the header - _buffer.put((byte) 'A'); - _buffer.put((byte) 'B'); + _buffer.put((byte)'A'); + _buffer.put((byte)'B'); addInt(0); - _buffer.put((byte) 0x4); + _buffer.put((byte)0x4); addInt(_status); if (_reason == null) - _reason=HttpGenerator.getReasonBuffer(_status); + _reason = HttpGenerator.getReasonBuffer(_status); if (_reason == null) _reason = new ByteArrayBuffer(Integer.toString(_status)); addBuffer(_reason); @@ -407,7 +399,6 @@ public class Ajp13Generator extends AbstractGenerator _content = null; } - // allocate 2 bytes for number of headers int field_index = _buffer.putIndex(); addInt(0); @@ -417,15 +408,15 @@ public class Ajp13Generator extends AbstractGenerator if (fields != null) { // Add headers - int s=fields.size(); - for (int f=0;f 0) ? 4 : 0) | ((_buffer != null && _buffer.length() > 0) ? 2 : 0); - + int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0); switch (to_flush) { - case 7: - throw new IllegalStateException(); // should - // never - // happen! - case 6: - len = _endp.flush(_header, _buffer, null); + case 7: + throw new IllegalStateException(); // should + // never + // happen! + case 6: + len = _endp.flush(_header,_buffer,null); - break; - case 5: - throw new IllegalStateException(); // should - // never - // happen! - case 4: - len = _endp.flush(_header); - break; - case 3: - throw new IllegalStateException(); // should - // never - // happen! - case 2: - len = _endp.flush(_buffer); + break; + case 5: + throw new IllegalStateException(); // should + // never + // happen! + case 4: + len = _endp.flush(_header); + break; + case 3: + throw new IllegalStateException(); // should + // never + // happen! + case 2: + len = _endp.flush(_buffer); - break; - case 1: - throw new IllegalStateException(); // should - // never - // happen! - case 0: - { - // Nothing more we can write now. - if (_header != null) - _header.clear(); - - _bufferPrepared = false; - - if (_buffer != null) + break; + case 1: + throw new IllegalStateException(); // should + // never + // happen! + case 0: { - _buffer.clear(); + // Nothing more we can write now. + if (_header != null) + _header.clear(); - // reserve some space for the - // header - _buffer.setPutIndex(7); - _buffer.setGetIndex(7); + _bufferPrepared = false; - // Special case handling for - // small left over buffer from - // an addContent that caused a - // buffer flush. - if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + if (_buffer != null) { + _buffer.clear(); + + // reserve some space for the + // header + _buffer.setPutIndex(7); + _buffer.setGetIndex(7); + + // Special case handling for + // small left over buffer from + // an addContent that caused a + // buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + + _buffer.put(_content); + _content.clear(); + _content = null; + break Flushing; + } + + } + + // Are we completely finished for now? + if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + + // if (_state == STATE_END) + // { + // _endp.close(); + // } + // - _buffer.put(_content); - _content.clear(); - _content = null; break Flushing; } + // Try to prepare more to write. + prepareBuffers(); } - - - - // Are we completely finished for now? - if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0)) - { - if (_state == STATE_FLUSHING) - _state = STATE_END; - -// if (_state == STATE_END) -// { -// _endp.close(); -// } -// - - break Flushing; - } - - // Try to prepare more to write. - prepareBuffers(); - } } // If we failed to flush anything twice in a row @@ -631,7 +618,7 @@ public class Ajp13Generator extends AbstractGenerator catch (IOException e) { LOG.ignore(e); - throw (e instanceof EofException) ? e : new EofException(e); + throw (e instanceof EofException)?e:new EofException(e); } } @@ -680,14 +667,14 @@ public class Ajp13Generator extends AbstractGenerator { _bufferPrepared = true; - _buffer.put((byte) 0); + _buffer.put((byte)0); int put = _buffer.putIndex(); _buffer.setGetIndex(0); _buffer.setPutIndex(0); - _buffer.put((byte) 'A'); - _buffer.put((byte) 'B'); + _buffer.put((byte)'A'); + _buffer.put((byte)'B'); addInt(payloadSize + 4); - _buffer.put((byte) 3); + _buffer.put((byte)3); addInt(payloadSize); _buffer.setPutIndex(put); } @@ -759,15 +746,15 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ private void addInt(int i) { - _buffer.put((byte) ((i >> 8) & 0xFF)); - _buffer.put((byte) (i & 0xFF)); + _buffer.put((byte)((i >> 8) & 0xFF)); + _buffer.put((byte)(i & 0xFF)); } /* ------------------------------------------------------------ */ private void addInt(int startIndex, int i) { - _buffer.poke(startIndex, (byte) ((i >> 8) & 0xFF)); - _buffer.poke((startIndex + 1), (byte) (i & 0xFF)); + _buffer.poke(startIndex,(byte)((i >> 8) & 0xFF)); + _buffer.poke((startIndex + 1),(byte)(i & 0xFF)); } /* ------------------------------------------------------------ */ @@ -786,7 +773,7 @@ public class Ajp13Generator extends AbstractGenerator addInt(b.length); _buffer.put(b); - _buffer.put((byte) 0); + _buffer.put((byte)0); } /* ------------------------------------------------------------ */ @@ -800,15 +787,14 @@ public class Ajp13Generator extends AbstractGenerator addInt(b.length()); _buffer.put(b); - _buffer.put((byte) 0); + _buffer.put((byte)0); } /* ------------------------------------------------------------ */ public void getBodyChunk() throws IOException { - _needMore = true; - _expectMore = true; - flushBuffer(); + ByteArrayBuffer bf = new ByteArrayBuffer(AJP13_MORE_CONTENT); + _endp.flush(bf); } /* ------------------------------------------------------------ */ @@ -818,7 +804,6 @@ public class Ajp13Generator extends AbstractGenerator _expectMore = false; } - /* ------------------------------------------------------------ */ public void sendCPong() throws IOException { @@ -831,13 +816,11 @@ public class Ajp13Generator extends AbstractGenerator { _endp.flush(buff); } - while(buff.length() >0); + while (buff.length() > 0); _buffers.returnBuffer(buff); - reset(true); + reset(); } - - } diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java index 02aa9e8fdd9..21c13c3e053 100644 --- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java @@ -148,21 +148,16 @@ public class Ajp13Parser implements Parser } /* ------------------------------------------------------------------------------- */ - public int parseAvailable() throws IOException + public boolean parseAvailable() throws IOException { - int len = parseNext(); - int total = len > 0 ? len : 0; - + boolean progress=parseNext()>0; + // continue parsing - while (!isComplete() && _buffer != null && _buffer.length() > 0) + while (!isComplete() && _buffer!=null && _buffer.length()>0) { - len = parseNext(); - if (len > 0) - total += len; - else - break; + progress |= parseNext()>0; } - return total; + return progress; } /* ------------------------------------------------------------------------------- */ @@ -876,6 +871,15 @@ public class Ajp13Parser implements Parser return _content.length() > 0; } + } + public boolean isPersistent() + { + return true; + } + + public void setPersistent(boolean persistent) + { + LOG.warn("AJP13.setPersistent is not IMPLEMENTED!"); } } diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java index bde26f7a2a3..df92aec440c 100644 --- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java @@ -13,7 +13,7 @@ package org.eclipse.jetty.ajp; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; public class Ajp13Request extends Request @@ -24,7 +24,7 @@ public class Ajp13Request extends Request protected boolean _sslSecure; /* ------------------------------------------------------------ */ - public Ajp13Request(HttpConnection connection) + public Ajp13Request(AbstractHttpConnection connection) { super(connection); } diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java index 39662b28915..d4e89323296 100644 --- a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java +++ b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java @@ -13,10 +13,10 @@ package org.eclipse.jetty.ajp; -import java.io.BufferedReader; +import static org.junit.Assert.assertTrue; + import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.net.SocketTimeoutException; @@ -39,8 +39,6 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.assertTrue; - public class Ajp13ConnectionTest { private static final Logger LOG = Log.getLogger(Ajp13ConnectionTest.class); diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java index 460e1f911bd..f6993b20133 100644 --- a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java +++ b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java @@ -13,6 +13,9 @@ package org.eclipse.jetty.ajp; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import org.eclipse.jetty.io.Buffer; @@ -23,9 +26,6 @@ import org.eclipse.jetty.io.SimpleBuffers; import org.eclipse.jetty.util.TypeUtil; import org.junit.Test; -import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class TestAjpParser { @Test diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index aa871a41e36..a4b6c219f44 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-annotations 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 84a8c00954a..bfe0031a442 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 @@ -454,7 +454,10 @@ public class AnnotationParser className = className.replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), className, false); if (resource!= null) - scanClass(resource.openStream()); + { + Resource r = Resource.newResource(resource); + scanClass(r.getInputStream()); + } } } } @@ -472,7 +475,10 @@ public class AnnotationParser String nameAsResource = cz.getName().replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), nameAsResource, false); if (resource!= null) - scanClass(resource.openStream()); + { + Resource r = Resource.newResource(resource); + scanClass(r.getInputStream()); + } } } if (visitSuperClasses) @@ -501,7 +507,10 @@ public class AnnotationParser s = s.replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), s, false); if (resource!= null) - scanClass(resource.openStream()); + { + Resource r = Resource.newResource(resource); + scanClass(r.getInputStream()); + } } } } @@ -525,7 +534,10 @@ public class AnnotationParser if (name.endsWith(".class")) { if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name)))) - scanClass(res.getURL().openStream()); + { + Resource r = Resource.newResource(res.getURL()); + scanClass(r.getInputStream()); + } } } diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index b9d194034dd..02a0320bdd9 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java similarity index 52% rename from jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java rename to jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java index 7dbd756f07f..a032b38b17b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java @@ -1,5 +1,5 @@ // ======================================================================== -// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// Copyright (c) 2006-2011 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 @@ -13,10 +13,8 @@ package org.eclipse.jetty.client; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,14 +29,13 @@ import org.eclipse.jetty.http.HttpSchemes; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.io.AbstractConnection; -import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.View; -import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; @@ -49,31 +46,29 @@ import org.eclipse.jetty.util.thread.Timeout; * * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $ */ -public class HttpConnection extends AbstractConnection implements Dumpable +public abstract class AbstractHttpConnection extends AbstractConnection implements Dumpable { - private static final Logger LOG = Log.getLogger(HttpConnection.class); + private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class); - private HttpDestination _destination; - private HttpGenerator _generator; - private HttpParser _parser; - private boolean _http11 = true; - private int _status; - private Buffer _connectionHeader; - private Buffer _requestContentChunk; - private boolean _requestComplete; - private boolean _reserved; + protected HttpDestination _destination; + protected HttpGenerator _generator; + protected HttpParser _parser; + protected boolean _http11 = true; + protected int _status; + protected Buffer _connectionHeader; + protected boolean _reserved; // The current exchange waiting for a response - private volatile HttpExchange _exchange; - private HttpExchange _pipeline; + protected volatile HttpExchange _exchange; + protected HttpExchange _pipeline; private final Timeout.Task _idleTimeout = new ConnectionIdleTask(); private AtomicBoolean _idle = new AtomicBoolean(false); - HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) + AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) { super(endp); - + _generator = new HttpGenerator(requestBuffers,endp); _parser = new HttpParser(responseBuffers,endp,new Handler()); } @@ -100,6 +95,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable public boolean send(HttpExchange ex) throws IOException { + LOG.debug("Send {} on {}",ex,this); synchronized (this) { if (_exchange != null) @@ -123,16 +119,6 @@ public class HttpConnection extends AbstractConnection implements Dumpable _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT); - if (_endp.isBlocking()) - { - this.notify(); - } - else - { - AsyncEndPoint scep = (AsyncEndPoint)_endp; - scep.scheduleWrite(); - } - adjustIdleTimeout(); return true; @@ -161,287 +147,8 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } - public Connection handle() throws IOException - { - try - { - int no_progress = 0; + public abstract Connection handle() throws IOException; - boolean failed = false; - while (_endp.isBufferingInput() || _endp.isOpen()) - { - synchronized (this) - { - while (_exchange == null) - { - if (_endp.isBlocking()) - { - try - { - this.wait(); - } - catch (InterruptedException e) - { - throw new InterruptedIOException(); - } - } - else - { - long filled = _parser.fill(); - if (filled < 0) - { - close(); - } - else - { - // Hopefully just space? - _parser.skipCRLF(); - if (_parser.isMoreInBuffer()) - { - LOG.warn("Unexpected data received but no request sent"); - close(); - } - } - return this; - } - } - } - - try - { - if (_exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT) - { - no_progress = 0; - commitRequest(); - } - - long io = 0; - _endp.flush(); - - if (_generator.isComplete()) - { - if (!_requestComplete) - { - _requestComplete = true; - _exchange.getEventListener().onRequestComplete(); - } - } - else - { - // Write as much of the request as possible - synchronized (this) - { - if (_exchange == null) - continue; - } - - long flushed = _generator.flushBuffer(); - io += flushed; - - if (!_generator.isComplete()) - { - if (_exchange!=null) - { - InputStream in = _exchange.getRequestContentSource(); - if (in != null) - { - if (_requestContentChunk == null || _requestContentChunk.length() == 0) - { - _requestContentChunk = _exchange.getRequestContentChunk(); - - if (_requestContentChunk != null) - _generator.addContent(_requestContentChunk,false); - else - _generator.complete(); - - flushed = _generator.flushBuffer(); - io += flushed; - } - } - else - _generator.complete(); - } - else - _generator.complete(); - } - } - - if (_generator.isComplete() && !_requestComplete) - { - _requestComplete = true; - _exchange.getEventListener().onRequestComplete(); - } - - // If we are not ended then parse available - if (!_parser.isComplete() && (_generator.isComplete() || _generator.isCommitted() && !_endp.isBlocking())) - { - long filled = _parser.parseAvailable(); - io += filled; - } - - if (io > 0) - no_progress = 0; - else if (no_progress++ >= 1 && !_endp.isBlocking()) - { - // SSL may need an extra flush as it may have made "no progress" while actually doing a handshake. - if (_endp instanceof SslSelectChannelEndPoint && !_generator.isComplete() && !_generator.isEmpty()) - { - long flushed = _generator.flushBuffer(); - if (flushed>0) - continue; - } - return this; - } - } - catch (Throwable e) - { - LOG.debug("Failure on " + _exchange, e); - - if (e instanceof ThreadDeath) - throw (ThreadDeath)e; - - failed = true; - - synchronized (this) - { - if (_exchange != null) - { - // Cancelling the exchange causes an exception as we close the connection, - // but we don't report it as it is normal cancelling operation - if (_exchange.getStatus() != HttpExchange.STATUS_CANCELLING && - _exchange.getStatus() != HttpExchange.STATUS_CANCELLED) - { - _exchange.setStatus(HttpExchange.STATUS_EXCEPTED); - _exchange.getEventListener().onException(e); - } - } - else - { - if (e instanceof IOException) - throw (IOException)e; - - if (e instanceof Error) - throw (Error)e; - - if (e instanceof RuntimeException) - throw (RuntimeException)e; - - throw new RuntimeException(e); - } - } - } - finally - { - boolean complete = false; - boolean close = failed; // always close the connection on error - if (!failed) - { - // are we complete? - if (_generator.isComplete()) - { - if (!_requestComplete) - { - _requestComplete = true; - _exchange.getEventListener().onRequestComplete(); - } - - // we need to return the HttpConnection to a state that - // it can be reused or closed out - if (_parser.isComplete()) - { - _exchange.cancelTimeout(_destination.getHttpClient()); - complete = true; - } - } - } - - if (_generator.isComplete() && !_parser.isComplete()) - { - if (!_endp.isOpen() || _endp.isInputShutdown()) - { - complete=true; - close=true; - close(); - } - } - - if (complete || failed) - { - synchronized (this) - { - if (!close) - close = shouldClose(); - - reset(true); - - no_progress = 0; - if (_exchange != null) - { - HttpExchange exchange=_exchange; - _exchange = null; - - // Reset the maxIdleTime because it may have been changed - if (!close) - _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout()); - - if (_status==HttpStatus.SWITCHING_PROTOCOLS_101) - { - Connection switched=exchange.onSwitchProtocol(_endp); - if (switched!=null) - { - // switched protocol! - exchange = _pipeline; - _pipeline = null; - if (exchange!=null) - _destination.send(exchange); - - return switched; - } - } - - if (_pipeline == null) - { - if (!isReserved()) - _destination.returnConnection(this, close); - } - else - { - if (close) - { - if (!isReserved()) - _destination.returnConnection(this,close); - - exchange = _pipeline; - _pipeline = null; - _destination.send(exchange); - } - else - { - exchange = _pipeline; - _pipeline = null; - send(exchange); - } - } - } - } - } - } - } - } - finally - { - _parser.returnBuffers(); - - // Do we have more stuff to write? - if (!_generator.isComplete() && _generator.getBytesBuffered()>0 && _endp instanceof AsyncEndPoint) - { - // Assume we are write blocked! - ((AsyncEndPoint)_endp).scheduleWrite(); - } - } - - return this; - } public boolean isIdle() { @@ -456,11 +163,11 @@ public class HttpConnection extends AbstractConnection implements Dumpable return false; } - public void closed() + public void onClose() { } - private void commitRequest() throws IOException + protected void commitRequest() throws IOException { synchronized (this) { @@ -472,7 +179,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable _generator.setVersion(_exchange.getVersion()); String method=_exchange.getMethod(); - String uri = _exchange.getURI(); + String uri = _exchange.getRequestURI(); if (_destination.isProxied() && !HttpMethods.CONNECT.equals(method) && uri.startsWith("/")) { boolean secure = _destination.isSecure(); @@ -536,28 +243,14 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } - protected void reset(boolean returnBuffers) throws IOException + protected void reset() throws IOException { - _requestComplete = false; _connectionHeader = null; _parser.reset(); - if (returnBuffers) - _parser.returnBuffers(); - _generator.reset(returnBuffers); + _generator.reset(); _http11 = true; } - private boolean shouldClose() - { - if (_connectionHeader!=null) - { - if (HttpHeaderValues.CLOSE_BUFFER.equals(_connectionHeader)) - return true; - if (HttpHeaderValues.KEEP_ALIVE_BUFFER.equals(_connectionHeader)) - return false; - } - return !_http11; - } private class Handler extends HttpParser.EventHandler { @@ -575,28 +268,33 @@ public class HttpConnection extends AbstractConnection implements Dumpable public void startResponse(Buffer version, int status, Buffer reason) throws IOException { HttpExchange exchange = _exchange; - if (exchange!=null) + if (exchange==null) { - switch(status) - { - case HttpStatus.CONTINUE_100: - case HttpStatus.PROCESSING_102: - // TODO check if appropriate expect was sent in the request. - exchange.setEventListener(new NonFinalResponseListener(exchange)); - break; - - case HttpStatus.OK_200: - // handle special case for CONNECT 200 responses - if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) - _parser.setHeadResponse(true); - break; - } - - _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version); - _status=status; - exchange.getEventListener().onResponseStatus(version,status,reason); - exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS); + LOG.warn("No exchange for response"); + _endp.close(); + return; } + + switch(status) + { + case HttpStatus.CONTINUE_100: + case HttpStatus.PROCESSING_102: + // TODO check if appropriate expect was sent in the request. + exchange.setEventListener(new NonFinalResponseListener(exchange)); + break; + + case HttpStatus.OK_200: + // handle special case for CONNECT 200 responses + if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) + _parser.setHeadResponse(true); + break; + } + + _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version); + _status=status; + exchange.getEventListener().onResponseStatus(version,status,reason); + exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS); + } @Override @@ -616,8 +314,6 @@ public class HttpConnection extends AbstractConnection implements Dumpable @Override public void headerComplete() throws IOException { - if (_endp instanceof AsyncEndPoint) - ((AsyncEndPoint)_endp).scheduleIdle(); HttpExchange exchange = _exchange; if (exchange!=null) exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT); @@ -626,8 +322,6 @@ public class HttpConnection extends AbstractConnection implements Dumpable @Override public void content(Buffer ref) throws IOException { - if (_endp instanceof AsyncEndPoint) - ((AsyncEndPoint)_endp).scheduleIdle(); HttpExchange exchange = _exchange; if (exchange!=null) exchange.getEventListener().onResponseContent(ref); @@ -640,12 +334,32 @@ public class HttpConnection extends AbstractConnection implements Dumpable if (exchange!=null) exchange.setStatus(HttpExchange.STATUS_COMPLETED); } + + @Override + public void earlyEOF() + { + HttpExchange exchange = _exchange; + if (exchange!=null) + { + if (!exchange.isDone()) + { + if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED)) + exchange.getEventListener().onException(new EofException("early EOF")); + } + } + } + + } @Override public String toString() { - return "HttpConnection@" + hashCode() + "//" + _destination.getAddress().getHost() + ":" + _destination.getAddress().getPort(); + return String.format("%s %s g=%s p=%s", + super.toString(), + _destination == null ? "?.?.?.?:??" : _destination.getAddress(), + _generator, + _parser); } public String toDetailString() @@ -669,15 +383,22 @@ public class HttpConnection extends AbstractConnection implements Dumpable case HttpExchange.STATUS_EXCEPTED: case HttpExchange.STATUS_EXPIRED: break; + case HttpExchange.STATUS_PARSING_CONTENT: + if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT)) + break; default: String exch= exchange.toString(); String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: "; - exchange.setStatus(HttpExchange.STATUS_EXCEPTED); - exchange.getEventListener().onException(new EOFException(reason+exch)); + if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED)) + exchange.getEventListener().onException(new EofException(reason+exch)); } } - _endp.close(); + if (_endp.isOpen()) + { + _endp.close(); + _destination.returnConnection(this, true); + } } public void setIdleTimeout() @@ -724,7 +445,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.util.component.Dumpable#dump() @@ -746,7 +467,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp)); } } - + /* ------------------------------------------------------------ */ private class ConnectionIdleTask extends Timeout.Task { @@ -757,18 +478,18 @@ public class HttpConnection extends AbstractConnection implements Dumpable // Connection idle, close it if (_idle.compareAndSet(true, false)) { - _destination.returnIdleConnection(HttpConnection.this); + _destination.returnIdleConnection(AbstractHttpConnection.this); } } } - - + + /* ------------------------------------------------------------ */ private class NonFinalResponseListener implements HttpEventListener { final HttpExchange _exchange; final HttpEventListener _next; - + /* ------------------------------------------------------------ */ public NonFinalResponseListener(HttpExchange exchange) { @@ -813,7 +534,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable { _exchange.setEventListener(_next); _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE); - _parser.reset(); + _parser.reset(); } /* ------------------------------------------------------------ */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java index f6a84649bb7..447b0628c46 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java @@ -43,6 +43,9 @@ public class Address public Address(String host, int port) { + if (host == null) + throw new IllegalArgumentException("Host is null"); + this.host = host.trim(); this.port = port; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java new file mode 100644 index 00000000000..455edfbeb92 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java @@ -0,0 +1,260 @@ +// ======================================================================== +// Copyright (c) 2006-2011 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 org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + + +/* ------------------------------------------------------------ */ +/** Asynchronous Client HTTP Connection + */ +public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection +{ + private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class); + + private boolean _requestComplete; + private Buffer _requestContentChunk; + private final AsyncEndPoint _asyncEndp; + + AsyncHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) + { + super(requestBuffers,responseBuffers,endp); + _asyncEndp=(AsyncEndPoint)endp; + } + + protected void reset() throws IOException + { + _requestComplete = false; + super.reset(); + } + + public Connection handle() throws IOException + { + Connection connection = this; + boolean progress=true; + + try + { + boolean failed = false; + + // While we are making progress and have not changed connection + while (progress && connection==this) + { + LOG.debug("while open={} more={} progress={}",_endp.isOpen(),_parser.isMoreInBuffer(),progress); + + progress=false; + HttpExchange exchange=_exchange; + + LOG.debug("exchange {} on {}",exchange,this); + + try + { + // Should we commit the request? + if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT) + { + LOG.debug("commit {}",exchange); + progress=true; + commitRequest(); + } + + // Generate output + if (_generator.isCommitted() && !_generator.isComplete()) + { + if (_generator.flushBuffer()>0) + { + LOG.debug("flushed"); + progress=true; + } + + // Is there more content to send or should we complete the generator + if (_generator.isState(AbstractGenerator.STATE_CONTENT)) + { + // Look for more content to send. + if (_requestContentChunk==null) + _requestContentChunk = exchange.getRequestContentChunk(null); + + if (_requestContentChunk==null) + { + LOG.debug("complete {}",exchange); + progress=true; + _generator.complete(); + } + else if (_generator.isEmpty()) + { + LOG.debug("addChunk"); + progress=true; + Buffer chunk=_requestContentChunk; + _requestContentChunk=exchange.getRequestContentChunk(null); + _generator.addContent(chunk,_requestContentChunk==null); + } + } + } + + // Signal request completion + if (_generator.isComplete() && !_requestComplete) + { + LOG.debug("requestComplete {}",exchange); + progress=true; + _requestComplete = true; + exchange.getEventListener().onRequestComplete(); + } + + // Read any input that is available + if (!_parser.isComplete() && _parser.parseAvailable()) + { + LOG.debug("parsed {}",exchange); + progress=true; + } + + // Flush output + _endp.flush(); + + // Has any IO been done by the endpoint itself since last loop + if (_asyncEndp.hasProgressed()) + { + LOG.debug("hasProgressed {}",exchange); + progress=true; + } + } + catch (Throwable e) + { + LOG.debug("Failure on " + _exchange, e); + + failed = true; + + synchronized (this) + { + if (exchange != null) + { + // Cancelling the exchange causes an exception as we close the connection, + // but we don't report it as it is normal cancelling operation + if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING && + exchange.getStatus() != HttpExchange.STATUS_CANCELLED && + !exchange.isDone()) + { + if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED)) + exchange.getEventListener().onException(e); + } + } + else + { + if (e instanceof IOException) + throw (IOException)e; + if (e instanceof Error) + throw (Error)e; + if (e instanceof RuntimeException) + throw (RuntimeException)e; + throw new RuntimeException(e); + } + } + } + finally + { + LOG.debug("finally {} on {} progress={} {}",exchange,this,progress,_endp); + + boolean complete = failed || _generator.isComplete() && _parser.isComplete(); + + if (complete) + { + boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent(); + _generator.setPersistent(persistent); + reset(); + if (persistent) + _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout()); + + synchronized (this) + { + exchange=_exchange; + _exchange = null; + + // Cancel the exchange + if (exchange!=null) + { + exchange.cancelTimeout(_destination.getHttpClient()); + + // TODO should we check the exchange is done? + } + + // handle switched protocols + if (_status==HttpStatus.SWITCHING_PROTOCOLS_101) + { + Connection switched=exchange.onSwitchProtocol(_endp); + if (switched!=null) + connection=switched; + { + // switched protocol! + _pipeline = null; + if (_pipeline!=null) + _destination.send(_pipeline); + _pipeline = null; + + connection=switched; + } + } + + // handle pipelined requests + if (_pipeline!=null) + { + if (!persistent || connection!=this) + _destination.send(_pipeline); + else + _exchange=_pipeline; + _pipeline=null; + } + + if (_exchange==null && !isReserved()) // TODO how do we return switched connections? + _destination.returnConnection(this, !persistent); + } + + } + } + } + } + finally + { + _parser.returnBuffers(); + _generator.returnBuffers(); + LOG.debug("unhandle {} on {}",_exchange,_endp); + } + + return connection; + } + + public void onInputShutdown() throws IOException + { + if (_generator.isIdle()) + _endp.shutdownOutput(); + } + + @Override + public boolean send(HttpExchange ex) throws IOException + { + boolean sent=super.send(ex); + if (sent) + _asyncEndp.asyncDispatch(); + return sent; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java new file mode 100644 index 00000000000..d241699bdac --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java @@ -0,0 +1,257 @@ +// ======================================================================== +// Copyright (c) 2006-2011 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.io.InterruptedIOException; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** Blocking HTTP Connection + */ +public class BlockingHttpConnection extends AbstractHttpConnection +{ + private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class); + + private boolean _requestComplete; + private Buffer _requestContentChunk; + + BlockingHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endPoint) + { + super(requestBuffers, responseBuffers, endPoint); + } + + protected void reset() throws IOException + { + _requestComplete = false; + super.reset(); + } + + @Override + public Connection handle() throws IOException + { + Connection connection = this; + + try + { + boolean failed = false; + + + // While we are making progress and have not changed connection + while (_endp.isOpen() && connection==this) + { + LOG.debug("open={} more={}",_endp.isOpen(),_parser.isMoreInBuffer()); + + HttpExchange exchange; + synchronized (this) + { + exchange=_exchange; + + while (exchange == null) + { + try + { + this.wait(); + exchange=_exchange; + } + catch (InterruptedException e) + { + throw new InterruptedIOException(); + } + } + } + LOG.debug("exchange {}",exchange); + + try + { + // Should we commit the request? + if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT) + { + LOG.debug("commit"); + commitRequest(); + } + + // Generate output + while (_generator.isCommitted() && !_generator.isComplete()) + { + if (_generator.flushBuffer()>0) + { + LOG.debug("flushed"); + } + + // Is there more content to send or should we complete the generator + if (_generator.isState(AbstractGenerator.STATE_CONTENT)) + { + // Look for more content to send. + if (_requestContentChunk==null) + _requestContentChunk = exchange.getRequestContentChunk(null); + + if (_requestContentChunk==null) + { + LOG.debug("complete"); + _generator.complete(); + } + else if (_generator.isEmpty()) + { + LOG.debug("addChunk"); + Buffer chunk=_requestContentChunk; + _requestContentChunk=exchange.getRequestContentChunk(null); + _generator.addContent(chunk,_requestContentChunk==null); + } + } + } + + // Signal request completion + if (_generator.isComplete() && !_requestComplete) + { + LOG.debug("requestComplete"); + _requestComplete = true; + exchange.getEventListener().onRequestComplete(); + } + + // Read any input that is available + if (!_parser.isComplete() && _parser.parseAvailable()) + { + LOG.debug("parsed"); + } + + // Flush output + _endp.flush(); + } + catch (Throwable e) + { + LOG.debug("Failure on " + _exchange, e); + + failed = true; + + synchronized (this) + { + if (exchange != null) + { + // Cancelling the exchange causes an exception as we close the connection, + // but we don't report it as it is normal cancelling operation + if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING && + exchange.getStatus() != HttpExchange.STATUS_CANCELLED && + !exchange.isDone()) + { + if(exchange.setStatus(HttpExchange.STATUS_EXCEPTED)) + exchange.getEventListener().onException(e); + } + } + else + { + if (e instanceof IOException) + throw (IOException)e; + if (e instanceof Error) + throw (Error)e; + if (e instanceof RuntimeException) + throw (RuntimeException)e; + throw new RuntimeException(e); + } + } + } + finally + { + LOG.debug("{} {}",_generator, _parser); + LOG.debug("{}",_endp); + + boolean complete = failed || _generator.isComplete() && _parser.isComplete(); + + if (complete) + { + boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent(); + _generator.setPersistent(persistent); + reset(); + if (persistent) + _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout()); + + synchronized (this) + { + exchange=_exchange; + _exchange = null; + + // Cancel the exchange + if (exchange!=null) + { + exchange.cancelTimeout(_destination.getHttpClient()); + + // TODO should we check the exchange is done? + } + + // handle switched protocols + if (_status==HttpStatus.SWITCHING_PROTOCOLS_101) + { + Connection switched=exchange.onSwitchProtocol(_endp); + if (switched!=null) + connection=switched; + { + // switched protocol! + _pipeline = null; + if (_pipeline!=null) + _destination.send(_pipeline); + _pipeline = null; + + connection=switched; + } + } + + // handle pipelined requests + if (_pipeline!=null) + { + if (!persistent || connection!=this) + _destination.send(_pipeline); + else + _exchange=_pipeline; + _pipeline=null; + } + + if (_exchange==null && !isReserved()) // TODO how do we return switched connections? + _destination.returnConnection(this, !persistent); + } + } + } + } + } + finally + { + _parser.returnBuffers(); + _generator.returnBuffers(); + } + + return connection; + } + + @Override + public boolean send(HttpExchange ex) throws IOException + { + boolean sent=super.send(ex); + if (sent) + { + synchronized (this) + { + notifyAll(); + } + } + return sent; + } +} 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 85b16242c91..21e8b0f89e9 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 @@ -16,6 +16,7 @@ package org.eclipse.jetty.client; import java.io.IOException; import java.io.InputStream; import java.net.UnknownHostException; +import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedList; import java.util.Set; @@ -29,13 +30,13 @@ import org.eclipse.jetty.client.security.RealmResolver; import org.eclipse.jetty.client.security.SecurityListener; import org.eclipse.jetty.http.HttpBuffers; import org.eclipse.jetty.http.HttpSchemes; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.io.Buffers.Type; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ThreadPool; import org.eclipse.jetty.util.thread.Timeout; @@ -52,11 +53,11 @@ import org.eclipse.jetty.util.thread.Timeout; * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method * to send a request. The exchange contains both the headers and content (source) of the request * plus the callbacks to handle responses. A HttpClient can have many exchanges outstanding - * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection}, - * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual + * and they may be queued on the {@link HttpDestination} waiting for a {@link AbstractHttpConnection}, + * queued in the {@link AbstractHttpConnection} waiting to be transmitted or pipelined on the actual * TCP/IP connection waiting for a response. *

- * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the + * The {@link HttpDestination} class is an aggregation of {@link AbstractHttpConnection}s for the * same host, port and protocol. A destination may limit the number of connections * open and they provide a pool of open connections that may be reused. Connections may also * be allocated from a destination, so that multiple request sources are not multiplexed @@ -164,7 +165,7 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable public void dump(Appendable out, String indent) throws IOException { out.append(String.valueOf(this)).append("\n"); - AggregateLifeCycle.dump(out,indent,_destinations.values()); + AggregateLifeCycle.dump(out,indent,Arrays.asList(_threadPool,_connector),_destinations.values()); } /* ------------------------------------------------------------------------------- */ @@ -178,10 +179,19 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable /* ------------------------------------------------------------ */ /** - * @return the threadPool + * @return the threadpool */ public ThreadPool getThreadPool() { + if (_threadPool==null) + { + QueuedThreadPool pool = new QueuedThreadPool(); + pool.setMaxThreads(16); + pool.setDaemon(true); + pool.setName("HttpClient"); + _threadPool = pool; + } + return _threadPool; } @@ -420,13 +430,7 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable _idleTimeoutQ.setNow(); if (_threadPool == null) - { - QueuedThreadPool pool = new QueuedThreadPool(); - pool.setMaxThreads(16); - pool.setDaemon(true); - pool.setName("HttpClient"); - _threadPool = pool; - } + getThreadPool(); if (_threadPool instanceof LifeCycle) { @@ -523,7 +527,7 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable /* ------------------------------------------------------------ */ /** - * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. + * @return the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed. */ public long getIdleTimeout() { @@ -532,7 +536,7 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable /* ------------------------------------------------------------ */ /** - * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed. + * @param ms the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed. */ public void setIdleTimeout(long ms) { @@ -695,14 +699,14 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable @Deprecated public String getKeyStoreLocation() { - return _sslContextFactory.getKeyStore(); + return _sslContextFactory.getKeyStorePath(); } /* ------------------------------------------------------------ */ @Deprecated public void setKeyStoreLocation(String keyStoreLocation) { - _sslContextFactory.setKeyStore(keyStoreLocation); + _sslContextFactory.setKeyStorePath(keyStoreLocation); } @Deprecated 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 6d243383db4..18a197ced6a 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 @@ -15,7 +15,7 @@ package org.eclipse.jetty.client; import java.io.IOException; import java.lang.reflect.Constructor; -import java.net.ConnectException; +import java.net.ProtocolException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -48,9 +48,9 @@ public class HttpDestination implements Dumpable private static final Logger LOG = Log.getLogger(HttpDestination.class); private final List _queue = new LinkedList(); - private final List _connections = new LinkedList(); + private final List _connections = new LinkedList(); private final BlockingQueue _newQueue = new ArrayBlockingQueue(10, true); - private final List _idle = new ArrayList(); + private final List _idle = new ArrayList(); private final HttpClient _client; private final Address _address; private final boolean _ssl; @@ -65,7 +65,7 @@ public class HttpDestination implements Dumpable private List _cookies; - + HttpDestination(HttpClient client, Address address, boolean ssl) { _client = client; @@ -168,9 +168,9 @@ public class HttpDestination implements Dumpable * @return a HttpConnection for this destination * @throws IOException if an I/O error occurs */ - private HttpConnection getConnection(long timeout) throws IOException + private AbstractHttpConnection getConnection(long timeout) throws IOException { - HttpConnection connection = null; + AbstractHttpConnection connection = null; while ((connection == null) && (connection = getIdleConnection()) == null && timeout > 0) { @@ -191,9 +191,9 @@ public class HttpDestination implements Dumpable try { Object o = _newQueue.take(); - if (o instanceof HttpConnection) + if (o instanceof AbstractHttpConnection) { - connection = (HttpConnection)o; + connection = (AbstractHttpConnection)o; } else throw (IOException)o; @@ -220,17 +220,17 @@ public class HttpDestination implements Dumpable return connection; } - public HttpConnection reserveConnection(long timeout) throws IOException + public AbstractHttpConnection reserveConnection(long timeout) throws IOException { - HttpConnection connection = getConnection(timeout); + AbstractHttpConnection connection = getConnection(timeout); if (connection != null) connection.setReserved(true); return connection; } - public HttpConnection getIdleConnection() throws IOException + public AbstractHttpConnection getIdleConnection() throws IOException { - HttpConnection connection = null; + AbstractHttpConnection connection = null; while (true) { synchronized (this) @@ -246,12 +246,16 @@ public class HttpDestination implements Dumpable } if (connection == null) + { return null; - + } + // Check if the connection was idle, // but it expired just a moment ago if (connection.cancelIdleTimeout()) + { return connection; + } } } @@ -290,8 +294,8 @@ public class HttpDestination implements Dumpable else if (_queue.size() > 0) { HttpExchange ex = _queue.remove(0); - ex.setStatus(HttpExchange.STATUS_EXCEPTED); - ex.getEventListener().onConnectionFailed(throwable); + if (ex.setStatus(HttpExchange.STATUS_EXCEPTED)) + ex.getEventListener().onConnectionFailed(throwable); // Since an existing connection had failed, we need to create a // connection if the queue is not empty and client is running. @@ -324,13 +328,13 @@ public class HttpDestination implements Dumpable if (_queue.size() > 0) { HttpExchange ex = _queue.remove(0); - ex.setStatus(HttpExchange.STATUS_EXCEPTED); - ex.getEventListener().onException(throwable); + if(ex.setStatus(HttpExchange.STATUS_EXCEPTED)) + ex.getEventListener().onException(throwable); } } } - public void onNewConnection(final HttpConnection connection) throws IOException + public void onNewConnection(final AbstractHttpConnection connection) throws IOException { Connection q_connection = null; @@ -352,9 +356,9 @@ public class HttpDestination implements Dumpable else { EndPoint endPoint = connection.getEndPoint(); - if (isProxied() && endPoint instanceof SelectConnector.ProxySelectChannelEndPoint) + if (isProxied() && endPoint instanceof SelectConnector.UpgradableEndPoint) { - SelectConnector.ProxySelectChannelEndPoint proxyEndPoint = (SelectConnector.ProxySelectChannelEndPoint)endPoint; + SelectConnector.UpgradableEndPoint proxyEndPoint = (SelectConnector.UpgradableEndPoint)endPoint; HttpExchange exchange = _queue.get(0); ConnectExchange connect = new ConnectExchange(getAddress(), proxyEndPoint, exchange); connect.setAddress(getProxy()); @@ -381,7 +385,7 @@ public class HttpDestination implements Dumpable } } - public void returnConnection(HttpConnection connection, boolean close) throws IOException + public void returnConnection(AbstractHttpConnection connection, boolean close) throws IOException { if (connection.isReserved()) connection.setReserved(false); @@ -433,16 +437,11 @@ public class HttpDestination implements Dumpable } } - public void returnIdleConnection(HttpConnection connection) + public void returnIdleConnection(AbstractHttpConnection connection) { - try - { - connection.close(); - } - catch (IOException e) - { - LOG.ignore(e); - } + // TODO work out the real idle time; + long idleForMs=connection!=null&&connection.getEndPoint()!=null?connection.getEndPoint().getMaxIdleTime():-1; + connection.onIdleExpired(idleForMs); boolean startConnection = false; synchronized (this) @@ -524,7 +523,7 @@ public class HttpDestination implements Dumpable // Add any known authorizations if (_authorizations != null) { - Authentication auth = (Authentication)_authorizations.match(ex.getURI()); + Authentication auth = (Authentication)_authorizations.match(ex.getRequestURI()); if (auth != null) (auth).setCredentials(ex); } @@ -533,7 +532,7 @@ public class HttpDestination implements Dumpable // so that we count also the queue time in the timeout ex.scheduleTimeout(this); - HttpConnection connection = getIdleConnection(); + AbstractHttpConnection connection = getIdleConnection(); if (connection != null) { send(connection, ex); @@ -566,7 +565,7 @@ public class HttpDestination implements Dumpable } } - protected void send(HttpConnection connection, HttpExchange exchange) throws IOException + protected void send(AbstractHttpConnection connection, HttpExchange exchange) throws IOException { synchronized (this) { @@ -584,7 +583,7 @@ public class HttpDestination implements Dumpable @Override public synchronized String toString() { - return "HttpDestination@" + hashCode() + "//" + _address.getHost() + ":" + _address.getPort() + "(" + _connections.size() + "," + _idle.size() + "," + _queue.size() + ")"; + return String.format("HttpDestination@%x//%s:%d(%d/%d,%d,%d/%d)%n",hashCode(),_address.getHost(),_address.getPort(),_connections.size(),_maxConnections,_idle.size(),_queue.size(),_maxQueueSize); } public synchronized String toDetailString() @@ -594,7 +593,7 @@ public class HttpDestination implements Dumpable b.append('\n'); synchronized (this) { - for (HttpConnection connection : _connections) + for (AbstractHttpConnection connection : _connections) { b.append(connection.toDetailString()); if (_idle.contains(connection)) @@ -637,7 +636,7 @@ public class HttpDestination implements Dumpable { synchronized (this) { - for (HttpConnection connection : _connections) + for (AbstractHttpConnection connection : _connections) { connection.close(); } @@ -665,13 +664,13 @@ public class HttpDestination implements Dumpable AggregateLifeCycle.dump(out,indent,_connections); } } - + private class ConnectExchange extends ContentExchange { - private final SelectConnector.ProxySelectChannelEndPoint proxyEndPoint; + private final SelectConnector.UpgradableEndPoint proxyEndPoint; private final HttpExchange exchange; - public ConnectExchange(Address serverAddress, SelectConnector.ProxySelectChannelEndPoint proxyEndPoint, HttpExchange exchange) + public ConnectExchange(Address serverAddress, SelectConnector.UpgradableEndPoint proxyEndPoint, HttpExchange exchange) { this.proxyEndPoint = proxyEndPoint; this.exchange = exchange; @@ -687,13 +686,18 @@ public class HttpDestination implements Dumpable @Override protected void onResponseComplete() throws IOException { - if (getResponseStatus() == HttpStatus.OK_200) + int responseStatus = getResponseStatus(); + if (responseStatus == HttpStatus.OK_200) { proxyEndPoint.upgrade(); } + else if(responseStatus == HttpStatus.GATEWAY_TIMEOUT_504) + { + onExpire(); + } else { - onConnectionFailed(new ConnectException(exchange.getAddress().toString())); + onException(new ProtocolException("Proxy: " + proxyEndPoint.getRemoteAddr() +":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus + " while trying to request: " + exchange.getAddress().toString())); } } @@ -702,5 +706,22 @@ public class HttpDestination implements Dumpable { HttpDestination.this.onConnectionFailed(x); } + + @Override + protected void onException(Throwable x) + { + _queue.remove(exchange); + if (exchange.setStatus(STATUS_EXCEPTED)) + exchange.getEventListener().onException(x); + } + + @Override + protected void onExpire() + { + _queue.remove(exchange); + if (exchange.setStatus(STATUS_EXPIRED)) + exchange.getEventListener().onExpire(); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index e97cc611654..27b28aa0cc8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -35,7 +35,9 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Timeout; /** - *

An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server.

+ *

+ * An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server. + *

* * This object encapsulates: *
    @@ -48,27 +50,29 @@ import org.eclipse.jetty.util.thread.Timeout; *
  • The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)} *
* - *

The HttpExchange class is intended to be used by a developer wishing to have close asynchronous - * interaction with the the exchange.
- * Typically a developer will extend the HttpExchange class with a derived - * class that overrides some or all of the onXxx callbacks.
- * There are also some predefined HttpExchange subtypes that can be used as a basis, - * see {@link org.eclipse.jetty.client.ContentExchange} and {@link org.eclipse.jetty.client.CachedExchange}.

+ *

+ * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous interaction with the the exchange.
+ * Typically a developer will extend the HttpExchange class with a derived class that overrides some or all of the onXxx callbacks.
+ * There are also some predefined HttpExchange subtypes that can be used as a basis, see {@link org.eclipse.jetty.client.ContentExchange} and + * {@link org.eclipse.jetty.client.CachedExchange}. + *

* - *

Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in - * turn selects a {@link HttpDestination} and calls its {@link HttpDestination#send(HttpExchange)}, which - * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange)}. - * A developer may wish to directly call send on the destination or connection if they wish to bypass - * some handling provided (eg Cookie handling in the HttpDestination).

+ *

+ * Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in turn selects a {@link HttpDestination} and calls its + * {@link HttpDestination#send(HttpExchange)}, which then creates or selects a {@link AbstractHttpConnection} and calls its {@link AbstractHttpConnection#send(HttpExchange)}. A + * developer may wish to directly call send on the destination or connection if they wish to bypass some handling provided (eg Cookie handling in the + * HttpDestination). + *

* - *

In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed - * pipeline request, authentication retry or redirection). In such cases, the HttpClient and/or HttpDestination - * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the - * HttpExchange.

+ *

+ * In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed pipeline request, authentication retry or redirection). + * In such cases, the HttpClient and/or HttpDestination may insert their own HttpExchangeListener to intercept and filter the call backs intended for the + * HttpExchange. + *

*/ public class HttpExchange { - private static final Logger LOG = Log.getLogger(HttpExchange.class); + static final Logger LOG = Log.getLogger(HttpExchange.class); public static final int STATUS_START = 0; public static final int STATUS_WAITING_FOR_CONNECTION = 1; @@ -94,21 +98,21 @@ public class HttpExchange private InputStream _requestContentSource; private AtomicInteger _status = new AtomicInteger(STATUS_START); - private Buffer _requestContentChunk; private boolean _retryStatus = false; // controls if the exchange will have listeners autoconfigured by the destination private boolean _configureListeners = true; private HttpEventListener _listener = new Listener(); - private volatile HttpConnection _connection; + private volatile AbstractHttpConnection _connection; private Address _localAddress = null; // a timeout for this exchange private long _timeout = -1; private volatile Timeout.Task _timeoutTask; - - private long _lastStateChange=-1; + private long _lastStateChange=System.currentTimeMillis(); private long _sent=-1; + private int _lastState=-1; + private int _lastStatePeriod=-1; boolean _onRequestCompleteDone; boolean _onResponseCompleteDone; @@ -118,9 +122,8 @@ public class HttpExchange { if (getStatus() < HttpExchange.STATUS_COMPLETED) setStatus(HttpExchange.STATUS_EXPIRED); - destination.exchangeExpired(this); - HttpConnection connection = _connection; + AbstractHttpConnection connection = _connection; if (connection != null) connection.exchangeExpired(this); } @@ -131,8 +134,10 @@ public class HttpExchange } /** - * @param status the status to wait for - * @throws InterruptedException if the waiting thread is interrupted + * @param status + * the status to wait for + * @throws InterruptedException + * if the waiting thread is interrupted * @deprecated Use {@link #waitForDone()} instead */ @Deprecated @@ -142,21 +147,17 @@ public class HttpExchange } /** - * Wait until the exchange is "done". - * Done is defined as when a final state has been passed to the - * HttpExchange via the associated onXxx call. Note that an - * exchange can transit a final state when being used as part - * of a dialog (eg {@link SecurityListener}. Done status - * is thus defined as:
-     *   done == onConnectionFailed
-     *        || onException
-     *        || onExpire
-     *        || onRequestComplete && onResponseComplete
+     * Wait until the exchange is "done". Done is defined as when a final state has been passed to the HttpExchange via the associated onXxx call. Note that an
+     * exchange can transit a final state when being used as part of a dialog (eg {@link SecurityListener}. Done status is thus defined as:
+     *
+     * 
+     * done == onConnectionFailed || onException || onExpire || onRequestComplete && onResponseComplete
      * 
+ * * @return the done status * @throws InterruptedException */ - public int waitForDone () throws InterruptedException + public int waitForDone() throws InterruptedException { synchronized (this) { @@ -170,25 +171,34 @@ public class HttpExchange { // TODO - this should do a cancel and wakeup everybody that was waiting. // might need a version number concept - synchronized(this) + synchronized (this) { - _timeoutTask=null; - _onRequestCompleteDone=false; - _onResponseCompleteDone=false; - _onDone=false; + _timeoutTask = null; + _onRequestCompleteDone = false; + _onResponseCompleteDone = false; + _onDone = false; setStatus(STATUS_START); } } - void setStatus(int newStatus) + /* ------------------------------------------------------------ */ + /** + * @param newStatus + * @return True if the status was actually set. + */ + boolean setStatus(int newStatus) { + boolean set = false; try { int oldStatus = _status.get(); - boolean set = false; - if (oldStatus!=newStatus) + boolean ignored = false; + if (oldStatus != newStatus) { - _lastStateChange=System.currentTimeMillis(); + long now = System.currentTimeMillis(); + _lastStatePeriod=(int)(now-_lastStateChange); + _lastState=oldStatus; + _lastStateChange=now; if (newStatus==STATUS_SENDING_REQUEST) _sent=_lastStateChange; } @@ -204,7 +214,10 @@ public class HttpExchange case STATUS_WAITING_FOR_COMMIT: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); + break; + case STATUS_EXPIRED: + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -214,11 +227,10 @@ public class HttpExchange case STATUS_WAITING_FOR_COMMIT: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -228,11 +240,10 @@ public class HttpExchange case STATUS_SENDING_REQUEST: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -240,16 +251,15 @@ public class HttpExchange switch (newStatus) { case STATUS_WAITING_FOR_RESPONSE: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onRequestCommitted(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -259,11 +269,10 @@ public class HttpExchange case STATUS_PARSING_HEADERS: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -271,16 +280,15 @@ public class HttpExchange switch (newStatus) { case STATUS_PARSING_CONTENT: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onResponseHeaderComplete(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -288,16 +296,15 @@ public class HttpExchange switch (newStatus) { case STATUS_COMPLETED: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onResponseComplete(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -307,12 +314,12 @@ public class HttpExchange case STATUS_START: case STATUS_EXCEPTED: case STATUS_WAITING_FOR_RESPONSE: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_CANCELLING: case STATUS_EXPIRED: // Don't change the status, it's too late - set=true; + ignored = true; break; } break; @@ -321,12 +328,12 @@ public class HttpExchange { case STATUS_EXCEPTED: case STATUS_CANCELLED: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) done(); break; default: // Ignore other statuses, we're cancelling - set=true; + ignored = true; break; } break; @@ -336,10 +343,16 @@ public class HttpExchange switch (newStatus) { case STATUS_START: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; + + case STATUS_COMPLETED: + ignored = true; + done(); + break; + default: - set=true; + ignored = true; break; } break; @@ -348,13 +361,23 @@ public class HttpExchange throw new AssertionError(oldStatus + " => " + newStatus); } - if (!set) + if (!set && !ignored) throw new IllegalStateException(toState(oldStatus) + " => " + toState(newStatus)); + LOG.debug("setStatus {} {}",newStatus,this); } catch (IOException x) { LOG.warn(x); } + return set; + } + + private boolean setStatusExpired(int newStatus, int oldStatus) + { + boolean set; + if (set = _status.compareAndSet(oldStatus,newStatus)) + getEventListener().onExpire(); + return set; } public boolean isDone() @@ -369,7 +392,7 @@ public class HttpExchange * @deprecated */ @Deprecated - public boolean isDone (int status) + public boolean isDone(int status) { return isDone(); } @@ -381,10 +404,10 @@ public class HttpExchange public void setEventListener(HttpEventListener listener) { - _listener=listener; + _listener = listener; } - public void setTimeout( long timeout ) + public void setTimeout(long timeout) { _timeout = timeout; } @@ -395,7 +418,8 @@ public class HttpExchange } /** - * @param url an absolute URL (for example 'http://localhost/foo/bar?a=1') + * @param url + * an absolute URL (for example 'http://localhost/foo/bar?a=1') */ public void setURL(String url) { @@ -403,7 +427,8 @@ public class HttpExchange } /** - * @param address the address of the server + * @param address + * the address of the server */ public void setAddress(Address address) { @@ -421,8 +446,7 @@ public class HttpExchange /** * the local address used by the connection * - * Note: this method will not be populated unless the exchange - * has been executed by the HttpClient + * Note: this method will not be populated unless the exchange has been executed by the HttpClient * * @return the local address used for the running of the exchange if available, null otherwise. */ @@ -432,15 +456,17 @@ public class HttpExchange } /** - * @param scheme the scheme of the URL (for example 'http') + * @param scheme + * the scheme of the URL (for example 'http') */ public void setScheme(Buffer scheme) { _scheme = scheme; } - + /** - * @param scheme the scheme of the URL (for example 'http') + * @param scheme + * the scheme of the URL (for example 'http') */ public void setScheme(String scheme) { @@ -464,7 +490,8 @@ public class HttpExchange } /** - * @param version the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1 + * @param version + * the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1 */ public void setVersion(int version) { @@ -472,7 +499,8 @@ public class HttpExchange } /** - * @param version the HTTP protocol version as string + * @param version + * the HTTP protocol version as string */ public void setVersion(String version) { @@ -493,7 +521,8 @@ public class HttpExchange } /** - * @param method the HTTP method (for example 'GET') + * @param method + * the HTTP method (for example 'GET') */ public void setMethod(String method) { @@ -528,9 +557,10 @@ public class HttpExchange } /** - * Set the request URI + * Set the request URI * - * @param uri new request URI + * @param uri + * new request URI * @see #setRequestURI(String) * @deprecated */ @@ -541,36 +571,42 @@ public class HttpExchange } /** - * Set the request URI + * Set the request URI * * Per RFC 2616 sec5, Request-URI = "*" | absoluteURI | abs_path | authority
- * where:

- * "*" - request applies to server itself
+ * where:
+ *
+ * "*" - request applies to server itself
* absoluteURI - required for proxy requests, e.g. http://localhost:8080/context
- * (this form is generated automatically by HttpClient)
- * abs_path - used for most methods, e.g. /context
- * authority - used for CONNECT method only, e.g. localhost:8080
+ * (this form is generated automatically by HttpClient)
+ * abs_path - used for most methods, e.g. /context
+ * authority - used for CONNECT method only, e.g. localhost:8080
*
* For complete definition of URI components, see RFC 2396 sec3.
- * - * @param uri new request URI + * + * @param uri + * new request URI */ public void setRequestURI(String uri) { _uri = uri; } - + /* ------------------------------------------------------------ */ /** - * @param uri an absolute URI (for example 'http://localhost/foo/bar?a=1') + * @param uri + * an absolute URI (for example 'http://localhost/foo/bar?a=1') */ public void setURI(URI uri) { if (!uri.isAbsolute()) - throw new IllegalArgumentException("!Absolute URI: "+uri); - + throw new IllegalArgumentException("!Absolute URI: " + uri); + if (uri.isOpaque()) - throw new IllegalArgumentException("Opaque URI: "+uri); + throw new IllegalArgumentException("Opaque URI: " + uri); + + if (LOG.isDebugEnabled()) + LOG.debug("URI = {}",uri.toASCIIString()); String scheme = uri.getScheme(); int port = uri.getPort(); @@ -582,13 +618,16 @@ public class HttpExchange HttpURI httpUri = new HttpURI(uri); String completePath = httpUri.getCompletePath(); - setRequestURI(completePath==null ? "/" : completePath); + setRequestURI(completePath == null?"/":completePath); } /** * Adds the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void addRequestHeader(String name, String value) { @@ -597,8 +636,11 @@ public class HttpExchange /** * Adds the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void addRequestHeader(Buffer name, Buffer value) { @@ -607,30 +649,37 @@ public class HttpExchange /** * Sets the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void setRequestHeader(String name, String value) { - getRequestFields().put(name, value); + getRequestFields().put(name,value); } /** * Sets the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void setRequestHeader(Buffer name, Buffer value) { - getRequestFields().put(name, value); + getRequestFields().put(name,value); } /** - * @param value the content type of the request + * @param value + * the content type of the request */ public void setRequestContentType(String value) { - getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER, value); + getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value); } /** @@ -642,7 +691,8 @@ public class HttpExchange } /** - * @param requestContent the request content + * @param requestContent + * the request content */ public void setRequestContent(Buffer requestContent) { @@ -650,7 +700,8 @@ public class HttpExchange } /** - * @param stream the request content as a stream + * @param stream + * the request content as a stream */ public void setRequestContentSource(InputStream stream) { @@ -667,25 +718,22 @@ public class HttpExchange return _requestContentSource; } - public Buffer getRequestContentChunk() throws IOException + public Buffer getRequestContentChunk(Buffer buffer) throws IOException { synchronized (this) { - if (_requestContentChunk == null) - _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure - else + if (_requestContentSource!=null) { - if (_requestContentChunk.hasContent()) - throw new IllegalStateException(); - _requestContentChunk.clear(); - } + if (buffer == null) + buffer = new ByteArrayBuffer(8192); // TODO configure - int read = _requestContentChunk.capacity(); - int length = _requestContentSource.read(_requestContentChunk.array(),0,read); - if (length >= 0) - { - _requestContentChunk.setPutIndex(length); - return _requestContentChunk; + int space = buffer.space(); + int length = _requestContentSource.read(buffer.array(),buffer.putIndex(),space); + if (length >= 0) + { + buffer.setPutIndex(buffer.putIndex()+length); + return buffer; + } } return null; } @@ -708,7 +756,8 @@ public class HttpExchange } /** - * @param retryStatus whether a retry will be attempted or not + * @param retryStatus + * whether a retry will be attempted or not */ public void setRetryStatus(boolean retryStatus) { @@ -716,13 +765,10 @@ public class HttpExchange } /** - * Initiates the cancelling of this exchange. - * The status of the exchange is set to {@link #STATUS_CANCELLING}. - * Cancelling the exchange is an asynchronous operation with respect to the request/response, - * and as such checking the request/response status of a cancelled exchange may return undefined results - * (for example it may have only some of the response headers being sent by the server). - * The cancelling of the exchange is completed when the exchange status (see {@link #getStatus()}) is - * {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}. + * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous + * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results + * (for example it may have only some of the response headers being sent by the server). The cancelling of the exchange is completed when the exchange + * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}. */ public void cancel() { @@ -732,17 +778,17 @@ public class HttpExchange private void done() { - synchronized(this) + synchronized (this) { disassociate(); - _onDone=true; + _onDone = true; notifyAll(); } } private void abort() { - HttpConnection httpConnection = _connection; + AbstractHttpConnection httpConnection = _connection; if (httpConnection != null) { try @@ -762,10 +808,10 @@ public class HttpExchange } } - void associate(HttpConnection connection) + void associate(AbstractHttpConnection connection) { - if (connection.getEndPoint().getLocalHost() != null) - _localAddress = new Address(connection.getEndPoint().getLocalHost(), connection.getEndPoint().getLocalPort()); + if (connection.getEndPoint().getLocalHost() != null) + _localAddress = new Address(connection.getEndPoint().getLocalHost(),connection.getEndPoint().getLocalPort()); _connection = connection; if (getStatus() == STATUS_CANCELLING) @@ -777,9 +823,9 @@ public class HttpExchange return this._connection != null; } - HttpConnection disassociate() + AbstractHttpConnection disassociate() { - HttpConnection result = _connection; + AbstractHttpConnection result = _connection; this._connection = null; if (getStatus() == STATUS_CANCELLING) setStatus(STATUS_CANCELLED); @@ -789,33 +835,60 @@ public class HttpExchange public static String toState(int s) { String state; - switch(s) + switch (s) { - case STATUS_START: state="START"; break; - case STATUS_WAITING_FOR_CONNECTION: state="CONNECTING"; break; - case STATUS_WAITING_FOR_COMMIT: state="CONNECTED"; break; - case STATUS_SENDING_REQUEST: state="SENDING"; break; - case STATUS_WAITING_FOR_RESPONSE: state="WAITING"; break; - case STATUS_PARSING_HEADERS: state="HEADERS"; break; - case STATUS_PARSING_CONTENT: state="CONTENT"; break; - case STATUS_COMPLETED: state="COMPLETED"; break; - case STATUS_EXPIRED: state="EXPIRED"; break; - case STATUS_EXCEPTED: state="EXCEPTED"; break; - case STATUS_CANCELLING: state="CANCELLING"; break; - case STATUS_CANCELLED: state="CANCELLED"; break; - default: state="UNKNOWN"; + case STATUS_START: + state = "START"; + break; + case STATUS_WAITING_FOR_CONNECTION: + state = "CONNECTING"; + break; + case STATUS_WAITING_FOR_COMMIT: + state = "CONNECTED"; + break; + case STATUS_SENDING_REQUEST: + state = "SENDING"; + break; + case STATUS_WAITING_FOR_RESPONSE: + state = "WAITING"; + break; + case STATUS_PARSING_HEADERS: + state = "HEADERS"; + break; + case STATUS_PARSING_CONTENT: + state = "CONTENT"; + break; + case STATUS_COMPLETED: + state = "COMPLETED"; + break; + case STATUS_EXPIRED: + state = "EXPIRED"; + break; + case STATUS_EXCEPTED: + state = "EXCEPTED"; + break; + case STATUS_CANCELLING: + state = "CANCELLING"; + break; + case STATUS_CANCELLED: + state = "CANCELLED"; + break; + default: + state = "UNKNOWN"; } return state; } - + @Override public String toString() { String state=toState(getStatus()); long now=System.currentTimeMillis(); long forMs = now -_lastStateChange; - String s= String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs); - if (getStatus()>=STATUS_SENDING_REQUEST) + String s= _lastState>=0 + ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs) + :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs); + if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0) s+="sent="+(now-_sent)+"ms"; return s; } @@ -828,79 +901,93 @@ public class HttpExchange } /** - * Callback called when the request headers have been sent to the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request headers have been sent to the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRequestCommitted() throws IOException { } /** - * Callback called when the request and its body have been sent to the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request and its body have been sent to the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRequestComplete() throws IOException { } /** - * Callback called when a response status line has been received from the server. - * This implementation does nothing. - * @param version the HTTP version - * @param status the HTTP status code - * @param reason the HTTP status reason string - * @throws IOException allowed to be thrown by overriding code + * Callback called when a response status line has been received from the server. This implementation does nothing. + * + * @param version + * the HTTP version + * @param status + * the HTTP status code + * @param reason + * the HTTP status reason string + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException { } /** - * Callback called for each response header received from the server. - * This implementation does nothing. - * @param name the header name - * @param value the header value - * @throws IOException allowed to be thrown by overriding code + * Callback called for each response header received from the server. This implementation does nothing. + * + * @param name + * the header name + * @param value + * the header value + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseHeader(Buffer name, Buffer value) throws IOException { } /** - * Callback called when the response headers have been completely received from the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the response headers have been completely received from the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseHeaderComplete() throws IOException { } /** - * Callback called for each chunk of the response content received from the server. - * This implementation does nothing. - * @param content the buffer holding the content chunk - * @throws IOException allowed to be thrown by overriding code + * Callback called for each chunk of the response content received from the server. This implementation does nothing. + * + * @param content + * the buffer holding the content chunk + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseContent(Buffer content) throws IOException { } /** - * Callback called when the entire response has been received from the server - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the entire response has been received from the server This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseComplete() throws IOException { } /** - * Callback called when an exception was thrown during an attempt to establish the connection - * with the server (for example the server is not listening). + * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening). * This implementation logs a warning. - * @param x the exception thrown attempting to establish the connection with the server + * + * @param x + * the exception thrown attempting to establish the connection with the server */ protected void onConnectionFailed(Throwable x) { @@ -908,9 +995,10 @@ public class HttpExchange } /** - * Callback called when any other exception occurs during the handling of this exchange. - * This implementation logs a warning. - * @param x the exception thrown during the handling of this exchange + * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning. + * + * @param x + * the exception thrown during the handling of this exchange */ protected void onException(Throwable x) { @@ -918,8 +1006,7 @@ public class HttpExchange } /** - * Callback called when no response has been received within the timeout. - * This implementation logs a warning. + * Callback called when no response has been received within the timeout. This implementation logs a warning. */ protected void onExpire() { @@ -927,9 +1014,10 @@ public class HttpExchange } /** - * Callback called when the request is retried (due to failures or authentication). - * Implementations must reset any consumable content that needs to be sent. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRetry() throws IOException { @@ -948,8 +1036,7 @@ public class HttpExchange } /** - * @return true if the exchange should have listeners configured for it by the destination, - * false if this is being managed elsewhere + * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere * @see #setConfigureListeners(boolean) */ public boolean configureListeners() @@ -958,7 +1045,8 @@ public class HttpExchange } /** - * @param autoConfigure whether the listeners are configured by the destination or elsewhere + * @param autoConfigure + * whether the listeners are configured by the destination or elsewhere */ public void setConfigureListeners(boolean autoConfigure) { @@ -981,7 +1069,7 @@ public class HttpExchange HttpClient httpClient = destination.getHttpClient(); long timeout = getTimeout(); if (timeout > 0) - httpClient.schedule(_timeoutTask, timeout); + httpClient.schedule(_timeoutTask,timeout); else httpClient.schedule(_timeoutTask); } @@ -1045,7 +1133,7 @@ public class HttpExchange } finally { - synchronized(HttpExchange.this) + synchronized (HttpExchange.this) { _onRequestCompleteDone = true; // Member _onDone may already be true, for example @@ -1066,7 +1154,7 @@ public class HttpExchange } finally { - synchronized(HttpExchange.this) + synchronized (HttpExchange.this) { _onResponseCompleteDone = true; // Member _onDone may already be true, for example @@ -1101,7 +1189,7 @@ public class HttpExchange public void onRetry() { - HttpExchange.this.setRetryStatus( true ); + HttpExchange.this.setRetryStatus(true); try { HttpExchange.this.onRetry(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java index b46d2bcc4fa..00bc4595432 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java @@ -110,25 +110,49 @@ public class RedirectListener extends HttpEventListenerWrapper if (_location != null) { if (_location.indexOf("://")>0) + { _exchange.setURL(_location); + } else + { _exchange.setRequestURI(_location); + } // destination may have changed - HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme()))); - + boolean isHttps = HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme())); + HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),isHttps); + if (_destination==destination) + { _destination.resend(_exchange); + } else { // unwrap to find ultimate listener. HttpEventListener listener=this; while(listener instanceof HttpEventListenerWrapper) + { listener=((HttpEventListenerWrapper)listener).getEventListener(); + } + //reset the listener _exchange.getEventListener().onRetry(); _exchange.reset(); _exchange.setEventListener(listener); + + // Set the new Host header + Address address = _exchange.getAddress(); + int port = address.getPort(); + StringBuilder hostHeader = new StringBuilder( 64 ); + hostHeader.append( address.getHost() ); + if( !( ( port == 80 && !isHttps ) || ( port == 443 && isHttps ) ) ) + { + hostHeader.append( ':' ); + hostHeader.append( port ); + } + + _exchange.setRequestHeader( HttpHeaders.HOST, hostHeader.toString() ); + destination.send(_exchange); } @@ -156,5 +180,28 @@ public class RedirectListener extends HttpEventListenerWrapper super.onRetry(); } -} + /** + * Delegate failed connection + */ + @Override + public void onConnectionFailed( Throwable ex ) + { + setDelegatingRequests(true); + setDelegatingResponses(true); + + super.onConnectionFailed( ex ); + } + + /** + * Delegate onException + */ + @Override + public void onException( Throwable ex ) + { + setDelegatingRequests(true); + setDelegatingResponses(true); + + super.onException( ex ); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java index 333c4166ba4..b3c93a7dee8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java @@ -17,39 +17,37 @@ import java.io.IOException; import java.net.SocketTimeoutException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.nio.channels.UnresolvedAddressException; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSession; -import org.eclipse.jetty.http.HttpGenerator; -import org.eclipse.jetty.http.HttpParser; -import org.eclipse.jetty.http.ssl.SslContextFactory; +import javax.net.ssl.SSLEngine; + +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; -import org.eclipse.jetty.io.Buffers; -import org.eclipse.jetty.io.Buffers.Type; -import org.eclipse.jetty.io.BuffersFactory; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.io.nio.SelectorManager; -import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SslConnection; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Timeout; +import org.eclipse.jetty.util.thread.Timeout.Task; -class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector +class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector, Dumpable { private static final Logger LOG = Log.getLogger(SelectConnector.class); private final HttpClient _httpClient; private final Manager _selectorManager=new Manager(); private final Map _connectingChannels = new ConcurrentHashMap(); - private SSLContext _sslContext; - private Buffers _sslBuffers; /** * @param httpClient the HttpClient this connector is associated to @@ -65,16 +63,6 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector { super.doStart(); - - final boolean direct=_httpClient.getUseDirectBuffers(); - - SSLEngine sslEngine=_selectorManager.newSslEngine(null); - final SSLSession ssl_session=sslEngine.getSession(); - _sslBuffers = BuffersFactory.newBuffers( - direct?Type.DIRECT:Type.INDIRECT,ssl_session.getApplicationBufferSize(), - direct?Type.DIRECT:Type.INDIRECT,ssl_session.getApplicationBufferSize(), - direct?Type.DIRECT:Type.INDIRECT,1024); - _selectorManager.start(); } @@ -85,35 +73,54 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector _selectorManager.stop(); } + public String dump() + { + return AggregateLifeCycle.dump(this); + } + + public void dump(Appendable out, String indent) throws IOException + { + out.append(String.valueOf(this)).append("\n"); + AggregateLifeCycle.dump(out, indent, Arrays.asList(_selectorManager)); + } + /* ------------------------------------------------------------ */ public void startConnection( HttpDestination destination ) throws IOException { + SocketChannel channel = null; try { - SocketChannel channel = SocketChannel.open(); + channel = SocketChannel.open(); Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress(); channel.socket().setTcpNoDelay(true); if (_httpClient.isConnectBlocking()) { - channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout()); - channel.configureBlocking(false); - _selectorManager.register( channel, destination ); + channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout()); + channel.configureBlocking(false); + _selectorManager.register( channel, destination ); } else { - channel.configureBlocking( false ); + channel.configureBlocking(false); channel.connect(address.toSocketAddress()); - _selectorManager.register( channel, destination ); - ConnectTimeout connectTimeout = new ConnectTimeout(channel, destination); + _selectorManager.register(channel,destination); + ConnectTimeout connectTimeout = new ConnectTimeout(channel,destination); _httpClient.schedule(connectTimeout,_httpClient.getConnectTimeout()); - _connectingChannels.put(channel, connectTimeout); + _connectingChannels.put(channel,connectTimeout); } - + } + catch (UnresolvedAddressException ex) + { + if (channel != null) + channel.close(); + destination.onConnectionFailed(ex); } catch(IOException ex) { + if (channel != null) + channel.close(); destination.onConnectionFailed(ex); } } @@ -121,6 +128,8 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector /* ------------------------------------------------------------ */ class Manager extends SelectorManager { + Logger LOG = SelectConnector.LOG; + @Override public boolean dispatch(Runnable task) { @@ -143,12 +152,9 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector } @Override - protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) { - if (endpoint instanceof SslSelectChannelEndPoint) - return new HttpConnection(_sslBuffers,_sslBuffers,endpoint); - - return new HttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint); + return new AsyncHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint); } @Override @@ -158,55 +164,48 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector Timeout.Task connectTimeout = _connectingChannels.remove(channel); if (connectTimeout != null) connectTimeout.cancel(); - LOG.debug("Channels with connection pending: {}", _connectingChannels.size()); + if (LOG.isDebugEnabled()) + LOG.debug("Channels with connection pending: {}", _connectingChannels.size()); // key should have destination at this point (will be replaced by endpoint after this call) HttpDestination dest=(HttpDestination)key.attachment(); - SelectChannelEndPoint ep=null; + SelectChannelEndPoint scep = new SelectChannelEndPoint(channel, selectSet, key, (int)_httpClient.getIdleTimeout()); + AsyncEndPoint ep = scep; if (dest.isSecure()) { - if (dest.isProxied()) - { - SSLEngine engine=newSslEngine(channel); - ep = new ProxySelectChannelEndPoint(channel, selectSet, key, _sslBuffers, engine, (int)_httpClient.getIdleTimeout()); - } - else - { - SSLEngine engine=newSslEngine(channel); - SslSelectChannelEndPoint sslEp = new SslSelectChannelEndPoint(_sslBuffers, channel, selectSet, key, engine, (int)_httpClient.getIdleTimeout()); - sslEp.setAllowRenegotiate(_httpClient.getSslContextFactory().isAllowRenegotiate()); - ep = sslEp; - } - } - else - { - ep = new SelectChannelEndPoint(channel, selectSet, key, (int)_httpClient.getIdleTimeout()); + LOG.debug("secure to {}, proxied={}",channel,dest.isProxied()); + ep = new UpgradableEndPoint(ep,newSslEngine(channel)); } - HttpConnection connection=(HttpConnection)ep.getConnection(); - connection.setDestination(dest); - dest.onNewConnection(connection); - return ep; + AsyncConnection connection = selectSet.getManager().newConnection(channel,ep, key.attachment()); + ep.setConnection(connection); + + AbstractHttpConnection httpConnection=(AbstractHttpConnection)connection; + httpConnection.setDestination(dest); + + if (dest.isSecure() && !dest.isProxied()) + ((UpgradableEndPoint)ep).upgrade(); + + dest.onNewConnection(httpConnection); + + return scep; } private synchronized SSLEngine newSslEngine(SocketChannel channel) throws IOException { SslContextFactory sslContextFactory = _httpClient.getSslContextFactory(); - if (_sslContext == null) - _sslContext = sslContextFactory.getSslContext(); - SSLEngine sslEngine; - if (channel != null && sslContextFactory.isSessionCachingEnabled()) + if (channel != null) { String peerHost = channel.socket().getInetAddress().getHostAddress(); int peerPort = channel.socket().getPort(); - sslEngine = _sslContext.createSSLEngine(peerHost, peerPort); + sslEngine = sslContextFactory.newSslEngine(peerHost, peerPort); } else { - sslEngine = _sslContext.createSSLEngine(); + sslEngine = sslContextFactory.newSslEngine(); } sslEngine.setUseClientMode(true); sslEngine.beginHandshake(); @@ -221,6 +220,10 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector @Override protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) { + Timeout.Task connectTimeout = _connectingChannels.remove(channel); + if (connectTimeout != null) + connectTimeout.cancel(); + if (attachment instanceof HttpDestination) ((HttpDestination)attachment).onConnectionFailed(ex); else @@ -259,203 +262,200 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector } } - /** - * An endpoint that is able to "upgrade" from a normal endpoint to a SSL endpoint. - * Since {@link HttpParser} and {@link HttpGenerator} only depend on the {@link EndPoint} - * interface, this class overrides all methods of {@link EndPoint} to provide the right - * behavior depending on the fact that it has been upgraded or not. - */ - public static class ProxySelectChannelEndPoint extends SslSelectChannelEndPoint + public static class UpgradableEndPoint implements AsyncEndPoint { - private final SelectChannelEndPoint plainEndPoint; - private volatile boolean upgraded = false; + AsyncEndPoint _endp; + SSLEngine _engine; - public ProxySelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, Buffers sslBuffers, SSLEngine engine, int maxIdleTimeout) throws IOException + public UpgradableEndPoint(AsyncEndPoint endp, SSLEngine engine) throws IOException { - super(sslBuffers, channel, selectSet, key, engine, maxIdleTimeout); - this.plainEndPoint = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTimeout); + _engine=engine; + _endp=endp; } public void upgrade() { - upgraded = true; + AsyncHttpConnection connection = (AsyncHttpConnection)_endp.getConnection(); + + SslConnection sslConnection = new SslConnection(_engine,_endp); + _endp.setConnection(sslConnection); + + _endp=sslConnection.getSslEndPoint(); + sslConnection.getSslEndPoint().setConnection(connection); + + LOG.debug("upgrade {} to {} for {}",this,sslConnection,connection); + } + + + public Connection getConnection() + { + return _endp.getConnection(); + } + + public void setConnection(Connection connection) + { + _endp.setConnection(connection); } public void shutdownOutput() throws IOException { - if (upgraded) - super.shutdownOutput(); - else - plainEndPoint.shutdownOutput(); + _endp.shutdownOutput(); + } + + public void asyncDispatch() + { + _endp.asyncDispatch(); + } + + public boolean isOutputShutdown() + { + return _endp.isOutputShutdown(); + } + + public void shutdownInput() throws IOException + { + _endp.shutdownInput(); + } + + public void scheduleWrite() + { + _endp.scheduleWrite(); + } + + public boolean isInputShutdown() + { + return _endp.isInputShutdown(); } public void close() throws IOException { - if (upgraded) - super.close(); - else - plainEndPoint.close(); + _endp.close(); } public int fill(Buffer buffer) throws IOException { - if (upgraded) - return super.fill(buffer); - else - return plainEndPoint.fill(buffer); + return _endp.fill(buffer); + } + + public boolean isWritable() + { + return _endp.isWritable(); + } + + public boolean hasProgressed() + { + return _endp.hasProgressed(); } public int flush(Buffer buffer) throws IOException { - if (upgraded) - return super.flush(buffer); - else - return plainEndPoint.flush(buffer); + return _endp.flush(buffer); + } + + public void scheduleTimeout(Task task, long timeoutMs) + { + _endp.scheduleTimeout(task,timeoutMs); + } + + public void cancelTimeout(Task task) + { + _endp.cancelTimeout(task); } public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException { - if (upgraded) - return super.flush(header, buffer, trailer); - else - return plainEndPoint.flush(header, buffer, trailer); + return _endp.flush(header,buffer,trailer); } public String getLocalAddr() { - if (upgraded) - return super.getLocalAddr(); - else - return plainEndPoint.getLocalAddr(); + return _endp.getLocalAddr(); } public String getLocalHost() { - if (upgraded) - return super.getLocalHost(); - else - return plainEndPoint.getLocalHost(); + return _endp.getLocalHost(); } public int getLocalPort() { - if (upgraded) - return super.getLocalPort(); - else - return plainEndPoint.getLocalPort(); + return _endp.getLocalPort(); } public String getRemoteAddr() { - if (upgraded) - return super.getRemoteAddr(); - else - return plainEndPoint.getRemoteAddr(); + return _endp.getRemoteAddr(); } public String getRemoteHost() { - if (upgraded) - return super.getRemoteHost(); - else - return plainEndPoint.getRemoteHost(); + return _endp.getRemoteHost(); } public int getRemotePort() { - if (upgraded) - return super.getRemotePort(); - else - return plainEndPoint.getRemotePort(); + return _endp.getRemotePort(); } public boolean isBlocking() { - if (upgraded) - return super.isBlocking(); - else - return plainEndPoint.isBlocking(); - } - - public boolean isBufferred() - { - if (upgraded) - return super.isBufferred(); - else - return plainEndPoint.isBufferred(); + return _endp.isBlocking(); } public boolean blockReadable(long millisecs) throws IOException { - if (upgraded) - return super.blockReadable(millisecs); - else - return plainEndPoint.blockReadable(millisecs); + return _endp.blockReadable(millisecs); } public boolean blockWritable(long millisecs) throws IOException { - if (upgraded) - return super.blockWritable(millisecs); - else - return plainEndPoint.blockWritable(millisecs); + return _endp.blockWritable(millisecs); } public boolean isOpen() { - if (upgraded) - return super.isOpen(); - else - return plainEndPoint.isOpen(); + return _endp.isOpen(); } public Object getTransport() { - if (upgraded) - return super.getTransport(); - else - return plainEndPoint.getTransport(); - } - - public boolean isBufferingInput() - { - if (upgraded) - return super.isBufferingInput(); - else - return plainEndPoint.isBufferingInput(); - } - - public boolean isBufferingOutput() - { - if (upgraded) - return super.isBufferingOutput(); - else - return plainEndPoint.isBufferingOutput(); + return _endp.getTransport(); } public void flush() throws IOException { - if (upgraded) - super.flush(); - else - plainEndPoint.flush(); - + _endp.flush(); } public int getMaxIdleTime() { - if (upgraded) - return super.getMaxIdleTime(); - else - return plainEndPoint.getMaxIdleTime(); + return _endp.getMaxIdleTime(); } public void setMaxIdleTime(int timeMs) throws IOException { - if (upgraded) - super.setMaxIdleTime(timeMs); - else - plainEndPoint.setMaxIdleTime(timeMs); + _endp.setMaxIdleTime(timeMs); } + + public void onIdleExpired(long idleForMs) + { + _endp.onIdleExpired(idleForMs); + } + + public void setCheckForIdle(boolean check) + { + _endp.setCheckForIdle(check); + } + + public boolean isCheckForIdle() + { + return _endp.isCheckForIdle(); + } + + public String toString() + { + return "Upgradable:"+_endp.toString(); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java index 226cd644ffd..fbcb9c28dcf 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java @@ -17,7 +17,6 @@ import java.io.InterruptedIOException; import java.net.Socket; import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -45,18 +44,9 @@ class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector public void startConnection(final HttpDestination destination) throws IOException { - Socket socket=null; - - if ( destination.isSecure() ) - { - SSLContext sslContext = _httpClient.getSSLContext(); - socket = sslContext.getSocketFactory().createSocket(); - } - else - { - LOG.debug("Using Regular Socket"); - socket = SocketFactory.getDefault().createSocket(); - } + Socket socket= destination.isSecure() + ?_httpClient.getSslContextFactory().newSslSocket() + :SocketFactory.getDefault().createSocket(); socket.setSoTimeout(0); socket.setTcpNoDelay(true); @@ -64,9 +54,9 @@ class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress(); socket.connect(address.toSocketAddress(), _httpClient.getConnectTimeout()); - EndPoint endpoint=new SocketEndPoint(socket); + final EndPoint endpoint=new SocketEndPoint(socket); - final HttpConnection connection=new HttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint); + final AbstractHttpConnection connection=new BlockingHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint); connection.setDestination(destination); destination.onNewConnection(connection); _httpClient.getThreadPool().dispatch(new Runnable() @@ -97,6 +87,17 @@ class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector destination.onException(e); } } + finally + { + try + { + destination.returnConnection(connection,true); + } + catch (IOException e) + { + LOG.debug(e); + } + } } }); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java index 2cd06974593..e0618b1268e 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java @@ -17,6 +17,10 @@ package org.eclipse.jetty.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; @@ -37,6 +41,14 @@ public abstract class AbstractConnectionTest // httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); return httpClient; } + + + protected ServerSocket newServerSocket() throws IOException + { + ServerSocket serverSocket=new ServerSocket(); + serverSocket.bind(null); + return serverSocket; + } @Test public void testServerClosedConnection() throws Exception @@ -57,6 +69,19 @@ public abstract class AbstractConnectionTest httpClient.send(exchange); Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + OutputStream output = remote.getOutputStream(); output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); output.write("Content-Length: 0\r\n".getBytes("UTF-8")); @@ -80,6 +105,15 @@ public abstract class AbstractConnectionTest httpClient.send(exchange); remote = serverSocket.accept(); + + input = remote.getInputStream(); + reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + output = remote.getOutputStream(); output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); output.write("Content-Length: 0\r\n".getBytes("UTF-8")); @@ -94,6 +128,112 @@ public abstract class AbstractConnectionTest } } + protected String getScheme() + { + return "http"; + } + + @Test + public void testServerClosedIncomplete() throws Exception + { + ServerSocket serverSocket = newServerSocket(); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = newHttpClient(); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setScheme(getScheme()); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 10\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + remote.close(); + + int status = exchange.waitForDone(); + + assertEquals(HttpExchange.STATUS_EXCEPTED, status); + + } + finally + { + httpClient.stop(); + } + } + + @Test + public void testServerHalfClosedIncomplete() throws Exception + { + ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(null); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = newHttpClient(); + httpClient.setIdleTimeout(10000); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 10\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + remote.shutdownOutput(); + + assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone()); + + } + finally + { + httpClient.stop(); + } + } + @Test public void testConnectionFailed() throws Exception { @@ -222,27 +362,33 @@ public abstract class AbstractConnectionTest HttpDestination dest = httpClient.getDestination(new Address("localhost", port),false); httpClient.send(exchange); - Socket s = serverSocket.accept(); + Socket server = serverSocket.accept(); + server.setSoTimeout(5000); byte[] buf = new byte[4096]; - s.getInputStream().read(buf); + + int len=server.getInputStream().read(buf); assertEquals(1,dest.getConnections()); assertEquals(0,dest.getIdleConnections()); - s.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes()); - - Thread.sleep(300); + server.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes()); + + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + Thread.sleep(200); // TODO get rid of this assertEquals(1,dest.getConnections()); assertEquals(1,dest.getIdleConnections()); exchange = new ConnectionExchange(); exchange.setAddress(new Address("localhost", port)); exchange.setRequestURI("/"); - httpClient.send(exchange); - s.getInputStream().read(buf); assertEquals(1,dest.getConnections()); assertEquals(0,dest.getIdleConnections()); - s.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes()); + + + len=server.getInputStream().read(buf); + assertEquals(1,dest.getConnections()); + assertEquals(0,dest.getIdleConnections()); + server.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes()); Thread.sleep(500); @@ -262,16 +408,16 @@ public abstract class AbstractConnectionTest } } - private class ConnectionExchange extends HttpExchange + protected class ConnectionExchange extends HttpExchange { private final CountDownLatch latch; - private ConnectionExchange() + protected ConnectionExchange() { this.latch = null; } - private ConnectionExchange(CountDownLatch latch) + protected ConnectionExchange(CountDownLatch latch) { this.latch = latch; } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java index cb5812780a2..c65525583d8 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java @@ -14,39 +14,37 @@ package org.eclipse.jetty.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.net.SocketTimeoutException; - import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StdErrLog; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + /** - * @version $Revision$ $Date$ */ public abstract class AbstractHttpExchangeCancelTest { - private static final Logger LOG = Log.getLogger(AbstractHttpExchangeCancelTest.class); - private Server server; private Connector connector; @@ -78,13 +76,13 @@ public abstract class AbstractHttpExchangeCancelTest TestHttpExchange exchange = new TestHttpExchange() { @Override - void setStatus(int status) + boolean setStatus(int status) { // Cancel before setting the new status if (getStatus() == HttpExchange.STATUS_START && status == STATUS_WAITING_FOR_CONNECTION) cancel(); - super.setStatus(status); + return super.setStatus(status); } }; exchange.setAddress(newAddress()); @@ -115,14 +113,15 @@ public abstract class AbstractHttpExchangeCancelTest TestHttpExchange exchange = new TestHttpExchange() { @Override - void setStatus(int status) + boolean setStatus(int status) { // Cancel after setting the new status int oldStatus = getStatus(); - super.setStatus(status); + boolean set = super.setStatus(status); if (oldStatus == STATUS_START && getStatus() == HttpExchange.STATUS_WAITING_FOR_CONNECTION) cancel(); + return set; } }; exchange.setAddress(newAddress()); @@ -186,10 +185,10 @@ public abstract class AbstractHttpExchangeCancelTest getHttpClient().send(exchange); int status = exchange.waitForDone(); - assertEquals(HttpExchange.STATUS_CANCELLED, status); - assertFalse(exchange.isResponseCompleted()); - assertFalse(exchange.isFailed()); - assertFalse(exchange.isAssociated()); + assertThat("Exchange Status", status, is(HttpExchange.STATUS_CANCELLED)); + assertThat("Exchange.isResponseCompleted", exchange.isResponseCompleted(), is(false)); + assertThat("Exchange.isFailed", exchange.isFailed(), is(false)); + assertThat("Exchange.isAssociated", exchange.isAssociated(), is(false)); } /* ------------------------------------------------------------ */ @@ -323,7 +322,7 @@ public abstract class AbstractHttpExchangeCancelTest { try { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true); TestHttpExchange exchange = new TestHttpExchange(); exchange.setAddress(newAddress()); exchange.setRequestURI("/?action=throw"); @@ -338,7 +337,7 @@ public abstract class AbstractHttpExchangeCancelTest } finally { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(false); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false); } } @@ -360,7 +359,7 @@ public abstract class AbstractHttpExchangeCancelTest int status = exchange.waitForDone(); long end = System.currentTimeMillis(); - + assertTrue(HttpExchange.STATUS_EXPIRED==status||HttpExchange.STATUS_EXCEPTED==status); assertFalse(exchange.isResponseCompleted()); assertTrue(end-start<4000); @@ -369,6 +368,26 @@ public abstract class AbstractHttpExchangeCancelTest assertFalse(exchange.isAssociated()); } + @Test + public void testHttpExchangeCancelReturnsConnection() throws Exception + { + TestHttpExchange exchange = new TestHttpExchange(); + Address address = newAddress(); + exchange.setAddress(address); + long delay = 5000; + exchange.setRequestURI("/?action=wait" + delay); + + HttpClient httpClient = getHttpClient(); + HttpDestination destination = httpClient.getDestination(address, false); + int connections = destination.getConnections(); + httpClient.send(exchange); + Thread.sleep(delay / 2); + Assert.assertEquals(connections + 1, destination.getConnections()); + + exchange.cancel(); + Assert.assertEquals(connections, destination.getConnections()); + } + /* ------------------------------------------------------------ */ protected abstract HttpClient getHttpClient(); @@ -447,6 +466,7 @@ public abstract class AbstractHttpExchangeCancelTest @Override protected synchronized void onException(Throwable ex) { + LOG.debug(ex); if (ex instanceof SocketTimeoutException || ex.getCause() instanceof SocketTimeoutException) expired=true; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java index 75dc89a1ed3..20a6d492e4f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java @@ -13,6 +13,13 @@ package org.eclipse.jetty.client; +import java.io.IOException; +import java.net.ServerSocket; + +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.BeforeClass; + public class AsyncSelectConnectionTest extends AbstractConnectionTest { protected HttpClient newHttpClient() @@ -22,4 +29,40 @@ public class AsyncSelectConnectionTest extends AbstractConnectionTest httpClient.setConnectBlocking(false); return httpClient; } + + static SslContextFactory ctx = new SslContextFactory(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath()); + + @BeforeClass + public static void initKS() throws Exception + { + ctx.setKeyStorePassword("storepwd"); + ctx.setKeyManagerPassword("keypwd"); + ctx.start(); + } + + @Override + protected String getScheme() + { + return "https"; + } + + @Override + protected ServerSocket newServerSocket() throws IOException + { + return ctx.newSslServerSocket(null,0,100); + } + + @Override + public void testServerHalfClosedIncomplete() throws Exception + { + // SSL doesn't do half closes + } + + @Override + public void testServerClosedIncomplete() throws Exception + { + super.testServerClosedIncomplete(); + } + + } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java index f5ac43dc403..875e224594c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java @@ -13,19 +13,36 @@ package org.eclipse.jetty.client; +import org.eclipse.jetty.client.helperClasses.AsyncSslServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.junit.Before; +import org.junit.Test; + public class AsyncSslHttpExchangeTest extends SslHttpExchangeTest { - @Override - public void setUp() throws Exception + private static ServerAndClientCreator serverAndClientCreator = new AsyncSslServerAndClientCreator(); + + @Before + public void setUpOnce() throws Exception { _scheme="https"; - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(2000); - _httpClient.setTimeout(2500); - _httpClient.setConnectTimeout(1000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(2); - _httpClient.start(); + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } + + + @Test + public void testPerf1() throws Exception + { + sender(1,true); + } + + + @Override + public void testBigPostWithContentExchange() throws Exception + { + super.testBigPostWithContentExchange(); + } + } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java index 1e7c1da3913..6c4d5102709 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java @@ -33,11 +33,26 @@ public class Curl client.start(); boolean async=true; boolean dump= false; + boolean verbose= false; - final CountDownLatch latch = new CountDownLatch(args.length); + + int urls=0; + for (String arg : args) + { + if (!arg.startsWith("-")) + urls++; + } + + final CountDownLatch latch = new CountDownLatch(urls); for (String arg : args) { + if ("--verbose".equals(arg)) + { + verbose=true; + continue; + } + if ("--sync".equals(arg)) { async=false; @@ -63,6 +78,7 @@ public class Curl } final boolean d = dump; + final boolean v = verbose; HttpExchange ex = new HttpExchange() { AtomicBoolean counted=new AtomicBoolean(false); @@ -105,7 +121,8 @@ public class Curl super.onResponseContent(content); if (d) System.out.print(content.toString()); - System.err.println("got "+content.length()); + if (v) + System.err.println("got "+content.length()); } /* ------------------------------------------------------------ */ @@ -116,7 +133,8 @@ public class Curl protected void onResponseHeader(Buffer name, Buffer value) throws IOException { super.onResponseHeader(name,value); - System.err.println(name+": "+value); + if (v) + System.err.println(name+": "+value); } /* ------------------------------------------------------------ */ @@ -127,7 +145,8 @@ public class Curl protected void onResponseHeaderComplete() throws IOException { super.onResponseHeaderComplete(); - System.err.println(); + if (v) + System.err.println(); } /* ------------------------------------------------------------ */ @@ -138,7 +157,8 @@ public class Curl protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException { super.onResponseStatus(version,status,reason); - System.err.println(version+" "+status+" "+reason); + if (v) + System.err.println(version+" "+status+" "+reason); } }; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java index 445947e3534..c61f767e5c6 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java @@ -13,35 +13,20 @@ package org.eclipse.jetty.client; -import java.io.FileInputStream; - -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.junit.Test; +import org.eclipse.jetty.client.helperClasses.ExternalKeyStoreAsyncSslServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.junit.Before; public class ExternalKeyStoreAsyncSslHttpExchangeTest extends SslHttpExchangeTest { - @Override - public void setUp() throws Exception + private static ServerAndClientCreator serverAndClientCreator = new ExternalKeyStoreAsyncSslServerAndClientCreator(); + + @Before + public void setUpOnce() throws Exception { - _scheme = "https"; - startServer(); - _httpClient = new HttpClient(); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(2); - - String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); - - _httpClient.setKeyStoreInputStream(new FileInputStream(keystore)); - _httpClient.setKeyStorePassword("storepwd"); - _httpClient.setKeyManagerPassword("keypwd"); - _httpClient.start(); + _scheme="https"; + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } - - @Override - @Test - public void testBigPostWithContentExchange() throws Exception - { - super.testBigPostWithContentExchange(); - } - } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java new file mode 100644 index 00000000000..e0410057571 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java @@ -0,0 +1,29 @@ +package org.eclipse.jetty.client; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.http.HttpFields; +import org.junit.Assert; + +public final class HttpAsserts +{ + public static void assertContainsHeaderKey(String expectedKey, HttpFields headers) + { + if (headers.containsKey(expectedKey)) + { + return; + } + List names = Collections.list(headers.getFieldNames()); + StringBuilder err = new StringBuilder(); + err.append("Missing expected header key [").append(expectedKey); + err.append("] (of ").append(names.size()).append(" header fields)"); + for (int i = 0; i < names.size(); i++) + { + String value = headers.getStringField(names.get(i)); + err.append("\n").append(i).append("] ").append(names.get(i)); + err.append(": ").append(value); + } + Assert.fail(err.toString()); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java index 09de4467204..d4fbf0fdd4b 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.matchers.JUnitMatchers.containsString; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -30,13 +29,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import org.eclipse.jetty.client.helperClasses.HttpServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; import org.eclipse.jetty.client.security.ProxyAuthorization; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.Buffer; @@ -44,15 +40,9 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.nio.DirectNIOBuffer; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.toolchain.test.Stress; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -63,37 +53,44 @@ import org.junit.Test; */ public class HttpExchangeTest { - private static final Logger LOG = Log.getLogger(HttpExchangeTest.class); + final static boolean verbose=HttpExchange.LOG.isDebugEnabled(); + protected static int _maxConnectionsPerAddress = 2; + protected static String _scheme = "http"; + protected static Server _server; + protected static int _port; + protected static HttpClient _httpClient; + protected static AtomicInteger _count = new AtomicInteger(); + protected static ServerAndClientCreator serverAndClientCreator = new HttpServerAndClientCreator(); - protected int _maxConnectionsPerAddress = 2; - protected String _scheme = "http"; - protected Server _server; - protected int _port; - protected HttpClient _httpClient; - protected Connector _connector; - protected AtomicInteger _count = new AtomicInteger(); + protected static URI getBaseURI() + { + return URI.create(_scheme + "://localhost:" + _port + "/"); + } /* ------------------------------------------------------------ */ + // TODO work out why BeforeClass does not work here? @Before - public void setUp() throws Exception + public void setUpOnce() throws Exception { - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(3000); - _httpClient.setTimeout(3500); - _httpClient.setConnectTimeout(2000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(_maxConnectionsPerAddress); - _httpClient.start(); + _scheme = "http"; + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } /* ------------------------------------------------------------ */ @After - public void tearDown() throws Exception + public void tearDownOnce() throws Exception { _httpClient.stop(); - Thread.sleep(500); - stopServer(); + long startTime = System.currentTimeMillis(); + while (!_httpClient.getState().equals(AbstractLifeCycle.STOPPED)) + { + if (System.currentTimeMillis() - startTime > 1000) + break; + Thread.sleep(5); + } + _server.stop(); } /* ------------------------------------------------------------ */ @@ -110,7 +107,9 @@ public class HttpExchangeTest { sender(1,false); sender(1,true); - + sender(10,false); + sender(10,true); + if (Stress.isEnabled()) { sender(100,false); @@ -118,11 +117,6 @@ public class HttpExchangeTest sender(10000,false); sender(10000,true); } - else - { - sender(10,false); - sender(10,true); - } } /* ------------------------------------------------------------ */ @@ -131,54 +125,65 @@ public class HttpExchangeTest * * @throws IOException */ - public void sender(final int nb,final boolean close) throws Exception + public void sender(final int nb, final boolean close) throws Exception { + // System.err.printf("%nSENDER %d %s%n",nb,close); _count.set(0); - final CountDownLatch complete=new CountDownLatch(nb); - final CountDownLatch latch=new CountDownLatch(nb); + final CountDownLatch complete = new CountDownLatch(nb); + final AtomicInteger allcontent = new AtomicInteger(nb); HttpExchange[] httpExchange = new HttpExchange[nb]; - long start=System.currentTimeMillis(); - for (int i=0; i "+len); } /* ------------------------------------------------------------ */ @Override protected void onResponseComplete() { - result="complete"; - if (len==2009) - latch.countDown(); + if (verbose) + System.err.println(n+" ] == "+len+" "+complete.getCount()+"/"+nb); + result = "complete"; + if (len == 2009) + allcontent.decrementAndGet(); else - { - System.err.println(n+" ONLY "+len); - } + System.err.println(n+ " ONLY " + len+ "/2009"); complete.countDown(); } @@ -207,9 +214,11 @@ public class HttpExchangeTest @Override protected void onConnectionFailed(Throwable ex) { + if (verbose) + System.err.println(n+" ] "+ex); complete.countDown(); - result="failed"; - System.err.println(n+" FAILED "+ex); + result = "failed"; + System.err.println(n+ " FAILED " + ex); super.onConnectionFailed(ex); } @@ -217,9 +226,11 @@ public class HttpExchangeTest @Override protected void onException(Throwable ex) { + if (verbose) + System.err.println(n+" ] "+ex); complete.countDown(); - result="excepted"; - System.err.println(n+" EXCEPTED "+ex); + result = "excepted"; + System.err.println(n+ " EXCEPTED " + ex); super.onException(ex); } @@ -227,9 +238,11 @@ public class HttpExchangeTest @Override protected void onExpire() { + if (verbose) + System.err.println(n+" ] expired"); complete.countDown(); - result="expired"; - System.err.println(n+" EXPIRED "+len); + result = "expired"; + System.err.println(n + " EXPIRED " + len); super.onExpire(); } @@ -237,11 +250,11 @@ public class HttpExchangeTest @Override public String toString() { - return n+" "+result+" "+len; + return n+"/"+result+"/"+len+"/"+super.toString(); } }; - httpExchange[n].setURL(_scheme+"://localhost:"+_port+"/"+n); + httpExchange[n].setURI(getBaseURI().resolve("/" + n)); httpExchange[n].addRequestHeader("arbitrary","value"); if (close) httpExchange[n].setRequestHeader("Connection","close"); @@ -249,17 +262,12 @@ public class HttpExchangeTest _httpClient.send(httpExchange[n]); } - assertTrue(complete.await(45,TimeUnit.SECONDS)); + if (!complete.await(2,TimeUnit.SECONDS)) + System.err.println(_httpClient.dump()); - long elapsed=System.currentTimeMillis()-start; - - // make windows-friendly ... System.currentTimeMillis() on windows is dope! - /* - if(elapsed>0) - System.err.println(nb+"/"+_count+" c="+close+" rate="+(nb*1000/elapsed)); - */ - - assertEquals("nb="+nb+" close="+close,0,latch.getCount()); + assertTrue(complete.await(20,TimeUnit.SECONDS)); + + assertEquals("nb="+nb+" close="+close,0,allcontent.get()); } /* ------------------------------------------------------------ */ @@ -269,7 +277,7 @@ public class HttpExchangeTest for (int i=0;i<20;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURI(new URI(_scheme, null, "localhost", _port, null, null, null)); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContent(new ByteArrayBuffer("")); _httpClient.send(httpExchange); @@ -288,19 +296,21 @@ public class HttpExchangeTest for (int i=0;i<10;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURI(new URI(_scheme, null, "localhost", _port, "/", "i="+i, null)); + URI uri = getBaseURI().resolve("?i=" + i); + httpExchange.setURI(uri); httpExchange.setMethod(HttpMethods.GET); _httpClient.send(httpExchange); int status = httpExchange.waitForDone(); //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); String result=httpExchange.getResponseContent(); + assertNotNull("Should have received response content", result); assertEquals("i="+i,0,result.indexOf("")); assertEquals("i="+i,result.length()-10,result.indexOf("")); assertEquals(HttpExchange.STATUS_COMPLETED, status); Thread.sleep(5); } } - + /* ------------------------------------------------------------ */ @Test public void testLocalAddressAvailabilityWithContentExchange() throws Exception @@ -308,30 +318,29 @@ public class HttpExchangeTest for (int i=0;i<10;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURL(_scheme+"://localhost:"+_port+"/?i="+i); + URI uri = getBaseURI().resolve("?i=" + i); + httpExchange.setURI(uri); httpExchange.setMethod(HttpMethods.GET); _httpClient.send(httpExchange); int status = httpExchange.waitForDone(); - + assertNotNull(httpExchange.getLocalAddress()); - - //System.out.println("Local Address: " + httpExchange.getLocalAddress()); - - //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); + String result=httpExchange.getResponseContent(); + assertNotNull("Should have received response content", result); assertEquals("i="+i,0,result.indexOf("")); assertEquals("i="+i,result.length()-10,result.indexOf("")); assertEquals(HttpExchange.STATUS_COMPLETED, status); Thread.sleep(5); } } - + /* ------------------------------------------------------------ */ @Test public void testShutdownWithExchange() throws Exception { final AtomicReference throwable=new AtomicReference(); - + HttpExchange httpExchange=new HttpExchange() { @@ -355,7 +364,7 @@ public class HttpExchangeTest throwable.set(x); } }; - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod("SLEEP"); _httpClient.send(httpExchange); new Thread() @@ -363,8 +372,8 @@ public class HttpExchangeTest @Override public void run() { - try { - Thread.sleep(500); + try { + Thread.sleep(500); _httpClient.stop(); } catch(Exception e) {e.printStackTrace();} } @@ -374,14 +383,62 @@ public class HttpExchangeTest System.err.println(throwable.get()); assertTrue(throwable.get().toString().indexOf("close")>=0); assertEquals(HttpExchange.STATUS_EXCEPTED, status); + _httpClient.start(); } /* ------------------------------------------------------------ */ @Test public void testBigPostWithContentExchange() throws Exception - { + { int size =32; - ContentExchange httpExchange=new ContentExchange(); + ContentExchange httpExchange=new ContentExchange() + { + int total; + + @Override + protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (verbose) + System.err.println("] "+version+" "+status+" "+reason); + super.onResponseStatus(version,status,reason); + } + + @Override + protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException + { + if (verbose) + System.err.println("] "+name+": "+value); + super.onResponseHeader(name,value); + } + + @Override + protected synchronized void onResponseContent(Buffer content) throws IOException + { + if (verbose) + { + total+=content.length(); + System.err.println("] "+content.length()+" -> "+total); + } + super.onResponseContent(content); + } + + @Override + protected void onRequestComplete() throws IOException + { + if (verbose) + System.err.println("] =="); + super.onRequestComplete(); + } + + @Override + protected void onResponseHeaderComplete() throws IOException + { + if (verbose) + System.err.println("] --"); + super.onResponseHeaderComplete(); + } + + }; Buffer babuf = new ByteArrayBuffer(size*36*1024); Buffer niobuf = new DirectNIOBuffer(size*36*1024); @@ -393,40 +450,67 @@ public class HttpExchangeTest babuf.put(bytes); niobuf.put(bytes); } - - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContentType("application/data"); httpExchange.setRequestContent(babuf); - _httpClient.send(httpExchange); - int status = httpExchange.waitForDone(); + long start=System.currentTimeMillis(); + while(!httpExchange.isDone()) + { + long now=System.currentTimeMillis(); + if ((now-start)>=10000) + { + System.err.println("TEST IS TAKING TOOOOO LONG!!!!!!!!!!!!!!!!!!!!"); + System.err.println("CLIENT:"); + System.err.println(_httpClient.dump()); + System.err.println("SERVER:"); + _server.dumpStdErr(); + break; + } + Thread.sleep(100); + } + int status = httpExchange.waitForDone(); assertEquals(HttpExchange.STATUS_COMPLETED,status); String result=httpExchange.getResponseContent(); assertEquals(babuf.length(),result.length()); httpExchange.reset(); - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContentType("application/data"); httpExchange.setRequestContent(niobuf); _httpClient.send(httpExchange); + + start=System.currentTimeMillis(); + while(!httpExchange.isDone()) + { + long now=System.currentTimeMillis(); + if ((now-start)>=10000) + { + System.err.println("TEST IS TAKING TOOOOO LONG!!!!!!!!!!!!!!!!!!!!"); + System.err.println("CLIENT:"); + System.err.println(_httpClient.dump()); + System.err.println("SERVER:"); + _server.dumpStdErr(); + break; + } + Thread.sleep(100); + } status = httpExchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, status); result=httpExchange.getResponseContent(); assertEquals(niobuf.length(),result.length()); - assertEquals(HttpExchange.STATUS_COMPLETED, status); } /* ------------------------------------------------------------ */ @Test public void testSlowPost() throws Exception { - ContentExchange httpExchange=new ContentExchange() - { - - }; - httpExchange.setURL(_scheme+"://localhost:"+_port); + ContentExchange httpExchange=new ContentExchange(); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); final String data="012345678901234567890123456789012345678901234567890123456789"; @@ -438,16 +522,28 @@ public class HttpExchangeTest @Override public int read() throws IOException { + // System.err.printf("reading 1 of %d/%d%n",_index,data.length()); if (_index>=data.length()) return -1; + try + { + Thread.sleep(5); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + // System.err.printf("read 1%n"); return data.charAt(_index++); } @Override public int read(byte[] b, int off, int len) throws IOException { - if (_index>=data.length()) + // System.err.printf("reading %d of %d/%d%n",len,_index,data.length()); + if (_index >= data.length()) return -1; try @@ -458,28 +554,27 @@ public class HttpExchangeTest { e.printStackTrace(); } - - int l=0; - while (l<5 && _index=0) + while ((len = in.read(buffer)) >= 0) { out.write(buffer,0,len); } } catch (EofException e) { - System.err.println("HttpExchangeTest#copyStream: "+e); + System.err.println("HttpExchangeTest#copyStream: " + e); } catch (IOException e) { e.printStackTrace(); } } - - /* ------------------------------------------------------------ */ - protected void newServer() throws Exception - { - _server=new Server(); - _server.setGracefulShutdown(500); - _connector=new SelectChannelConnector(); - - _connector.setMaxIdleTime(3000000); - - _connector.setPort(0); - _server.setConnectors(new Connector[] { _connector }); - } - - /* ------------------------------------------------------------ */ - protected void startServer() throws Exception - { - newServer(); - _server.setHandler(new AbstractHandler() - { - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - int i=0; - try - { - baseRequest.setHandled(true); - response.setStatus(200); - _count.incrementAndGet(); - - if (request.getServerName().equals("jetty.eclipse.org")) - { - response.getOutputStream().println("Proxy request: "+request.getRequestURL()); - response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION)); - } - else if (request.getMethod().equalsIgnoreCase("GET")) - { - response.getOutputStream().println(""); - for (; i<100; i++) - { - response.getOutputStream().println(" "+i+""); - } - else if (request.getMethod().equalsIgnoreCase("OPTIONS")) - { - if ("*".equals(target)) - { - response.setContentLength(0); - response.setHeader("Allow","GET,HEAD,POST,PUT,DELETE,MOVE,OPTIONS,TRACE"); - } - } - else if (request.getMethod().equalsIgnoreCase("SLEEP")) - { - Thread.sleep(10000); - } - else - { - response.setContentType(request.getContentType()); - int size=request.getContentLength(); - ByteArrayOutputStream bout = new ByteArrayOutputStream(size>0?size:32768); - IO.copy(request.getInputStream(),bout); - response.getOutputStream().write(bout.toByteArray()); - } - } - catch(InterruptedException e) - { - LOG.debug(e); - } - catch(IOException e) - { - e.printStackTrace(); - throw e; - } - catch(Throwable e) - { - e.printStackTrace(); - throw new ServletException(e); - } - finally - { - } - } - }); - _server.start(); - _port=_connector.getLocalPort(); - } - - /* ------------------------------------------------------------ */ - private void stopServer() throws Exception - { - _server.stop(); - } - } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java new file mode 100644 index 00000000000..91d241f0b0d --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java @@ -0,0 +1,136 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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 static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.ProtocolException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ConnectHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/* ------------------------------------------------------------ */ +/** + * This UnitTest class executes two tests. Both will send a http request to https://google.com through a misbehaving proxy server. + *

+ * The first test runs against a proxy which simply closes the connection (as nginx does) for a connect request. The second proxy server always responds with a + * 500 error. + *

+ * The expected result for both tests is an exception and the HttpExchange should have status HttpExchange.STATUS_EXCEPTED. + */ +public class HttpsViaBrokenHttpProxyTest +{ + private Server _proxy = new Server(); + private HttpClient _client = new HttpClient(); + + @Before + public void init() throws Exception + { + // setup proxies with different behaviour + _proxy.addConnector(new SelectChannelConnector()); + _proxy.setHandler(new BadBehavingConnectHandler()); + _proxy.start(); + int proxyClosingConnectionPort = _proxy.getConnectors()[0].getLocalPort(); + + _client.setProxy(new Address("localhost", proxyClosingConnectionPort)); + _client.start(); + } + + @After + public void destroy() throws Exception + { + _client.stop(); + _proxy.stop(); + } + + @Test + public void httpsViaProxyThatClosesConnectionOnConnectRequestTest() throws Exception + { + sendRequestThroughProxy(new ContentExchange() + { + + @Override + protected void onException(Throwable x) + { + + } + + }, "close", 9); + } + + @Test + public void httpsViaProxyThatReturns500ErrorTest() throws Exception + { + HttpExchange exchange = new ContentExchange() + { + @Override + protected void onException(Throwable x) + { + // Suppress logging for expected exception + if (!(x instanceof ProtocolException)) + super.onException(x); + } + }; + sendRequestThroughProxy(exchange, "error500", 9); + } + + @Test + public void httpsViaProxyThatReturns504ErrorTest() throws Exception + { + sendRequestThroughProxy(new ContentExchange(), "error504", 8); + } + + private void sendRequestThroughProxy(HttpExchange exchange, String desiredBehaviour, int exptectedStatus) throws Exception + { + String url = "https://" + desiredBehaviour + ".com/"; + exchange.setURL(url); + exchange.addRequestHeader("behaviour", desiredBehaviour); + _client.send(exchange); + assertEquals(HttpExchange.toState(exptectedStatus) + " status awaited", exptectedStatus, exchange.waitForDone()); + } + + private class BadBehavingConnectHandler extends ConnectHandler + { + @Override + protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) + throws ServletException, IOException + { + if (serverAddress.contains("close")) + { + AbstractHttpConnection.getCurrentConnection().getEndPoint().close(); + } + else if (serverAddress.contains("error500")) + { + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + else if (serverAddress.contains("error504")) + { + response.setStatus(HttpStatus.GATEWAY_TIMEOUT_504); + } + baseRequest.setHandled(true); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java index 7fdde5b2a2c..9950d6710fb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java @@ -51,4 +51,10 @@ public class NonBlockingHttpExchangeCancelTest extends AbstractHttpExchangeCance { return httpClient; } + + /* ------------------------------------------------------------ */ + public void testHttpExchangeCancelOnRequestComplete() throws Exception + { + super.testHttpExchangeCancelOnRequestComplete(); + } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java index f56e6a0a686..89a55f510e0 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java @@ -1,9 +1,13 @@ package org.eclipse.jetty.client; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.net.URLEncoder; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; + import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -12,7 +16,6 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; @@ -23,12 +26,10 @@ import org.eclipse.jetty.server.handler.ConnectHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.After; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class ProxyTunnellingTest { private Server server; @@ -41,13 +42,13 @@ public class ProxyTunnellingTest { return proxyConnector.getLocalPort(); } - + protected void startSSLServer(Handler handler) throws Exception { SslSelectChannelConnector connector = new SslSelectChannelConnector(); String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keyStorePath); + cf.setKeyStorePath(keyStorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); startServer(connector, handler); @@ -217,11 +218,11 @@ public class ProxyTunnellingTest ContentExchange exchange = new ContentExchange(true) { @Override - protected void onConnectionFailed(Throwable x) + protected void onException(Throwable x) { latch.countDown(); } - + }; exchange.setMethod(HttpMethods.GET); String body = "BODY"; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java index b7883b229ab..9016b50a8f2 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java @@ -19,7 +19,6 @@ import java.util.HashSet; import java.util.Set; import org.eclipse.jetty.client.security.Realm; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -33,6 +32,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; public class SecuredContentExchangeTest extends ContentExchangeTest diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java index 7af4be89281..00a99a8f1fd 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java @@ -20,7 +20,6 @@ import java.util.Set; import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -34,6 +33,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; import org.junit.Test; public class SecuredErrorStatusTest diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java index 133cf476c92..8a1e8a50e94 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java @@ -31,7 +31,6 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.client.security.SimpleRealmResolver; import org.eclipse.jetty.http.HttpMethods; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.security.ConstraintMapping; @@ -46,6 +45,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java new file mode 100644 index 00000000000..bc476c27fc7 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java @@ -0,0 +1,211 @@ +package org.eclipse.jetty.client; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + + +/* ------------------------------------------------------------ */ +/** + */ +public class Siege +{ + private static final class ConcurrentExchange extends HttpExchange + { + private final long _start=System.currentTimeMillis(); + private final HttpClient _client; + private final CountDownLatch _latch; + volatile int _status; + volatile int _count; + volatile long _bytes; + final List _uris; + final int _repeats; + int _u; + int _r; + + AtomicBoolean counted=new AtomicBoolean(false); + + public ConcurrentExchange(HttpClient client,CountDownLatch latch, List uris, int repeats) + { + _client = client; + _latch = latch; + _uris = uris; + _repeats = repeats; + } + + @Override + protected void onConnectionFailed(Throwable ex) + { + if (!counted.getAndSet(true)) + _latch.countDown(); + super.onConnectionFailed(ex); + } + + @Override + protected void onException(Throwable ex) + { + if (!counted.getAndSet(true)) + _latch.countDown(); + super.onException(ex); + } + + @Override + protected void onExpire() + { + if (!counted.getAndSet(true)) + _latch.countDown(); + super.onExpire(); + } + + @Override + protected void onResponseComplete() throws IOException + { + if (_status==200) + _count++; + if (!next() && !counted.getAndSet(true)) + { + _latch.countDown(); + long duration=System.currentTimeMillis()-_start; + System.err.printf("Got %d/%d with %dB in %dms %d%n",_count,_uris.size()*_repeats,_bytes,duration,_latch.getCount()); + } + } + + + /* ------------------------------------------------------------ */ + @Override + protected void onResponseContent(Buffer content) throws IOException + { + _bytes+=content.length(); + super.onResponseContent(content); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.client.HttpExchange#onResponseHeader(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer) + */ + @Override + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + super.onResponseHeader(name,value); + if ("Set-Cookie".equalsIgnoreCase(name.toString())) + { + String v=value.toString(); + int c = v.indexOf(';'); + if (c>=0) + v=v.substring(0,c); + addRequestHeader("Cookie",v); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.client.HttpExchange#onResponseHeaderComplete() + */ + @Override + protected void onResponseHeaderComplete() throws IOException + { + super.onResponseHeaderComplete(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.client.HttpExchange#onResponseStatus(org.eclipse.jetty.io.Buffer, int, org.eclipse.jetty.io.Buffer) + */ + @Override + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + _status=status; + super.onResponseStatus(version,status,reason); + } + + public boolean next() + { + if (_u>=_uris.size()) + { + _u=0; + _r++; + if (_r>=_repeats) + return false; + } + + String uri=_uris.get(_u++); + + reset(); + setMethod(HttpMethods.GET); + setURL(uri); + + try + { + _client.send(this); + } + catch(IOException e) + { + e.printStackTrace(); + return false; + } + return true; + } + } + + public static void main(String[] args) + throws Exception + { + if (args.length==0) + args=new String[] + { "-c", "2", "-r", "2", "http://localhost:8080/dump", "http://localhost:8080/d.txt"}; + + int concurrent=1; + int repeats=1; + final List uris = new ArrayList(); + + for (int i=0; i accumulatedRequest; + + public SluggishHandler(int requestSize) + { + accumulatedRequest = new ArrayList(requestSize); + } + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + accumulatedRequest.clear(); + ServletInputStream input = request.getInputStream(); + byte[] buffer = new byte[16384]; + int bytesAvailable; + while ((bytesAvailable = input.read(buffer,0,buffer.length)) > 0) + { + //System.err.println("AVAILABLE FOR READ = " + bytesAvailable); + for (int n = 0; n < bytesAvailable; ++n) + { + accumulatedRequest.add(buffer[n]); + } + try + { + Thread.sleep(READ_DELAY); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + response.setStatus(HttpServletResponse.SC_OK); + baseRequest.setHandled(true); + //System.err.println("HANDLED"); + } + + public byte[] getAccumulatedRequest() + { + byte[] buffer = new byte[accumulatedRequest.size()]; + int pos = 0; + for (Byte b : accumulatedRequest) + { + buffer[pos++] = b; + } + return buffer; + } + } + + private static boolean compareBuffers(byte[] sent, byte[] received) + { + if (sent.length != received.length) + { + System.err.format("Mismatch in sent/received lengths: sent=%d received=%d\n",sent.length,received.length); + return false; + } + else + { + for (int n = 0; n < sent.length; ++n) + { + if (sent[n] != received[n]) + { + System.err.format("Mismatch at offset %d: request=%d response=%d\n",n,sent[n],received[n]); + return false; + } + } + } + return true; + } + + @Test + public void test0() throws Exception + { + goSlow(20000,10); + } + + @Test + public void test1() throws Exception + { + goSlow(200000,5); + } + + @Test + public void test2() throws Exception + { + goSlow(2000000,2); + } + + void goSlow(int requestSize,int iterations) throws Exception + { + Server server = new Server(); + SocketConnector connector = new SocketConnector(); + server.addConnector(connector); + SluggishHandler handler = new SluggishHandler(requestSize); + server.setHandler(handler); + server.start(); + int port = connector.getLocalPort(); + + HttpClient client = new HttpClient(); + client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + client.setConnectTimeout(5000); + client.setIdleTimeout(60000); + client.start(); + + try + { + for (int i = 0; i < iterations; ++i) + { + //System.err.format("-------------- ITERATION %d ------------------\n",i); + SluggishExchange exchange = new SluggishExchange(port,requestSize); + long startTime = System.currentTimeMillis(); + client.send(exchange); + exchange.waitForDone(); + long endTime = System.currentTimeMillis(); + //System.err.println("EXCHANGE STATUS = " + exchange); + //System.err.println("ELAPSED MSEC = " + (endTime - startTime)); + Assert.assertTrue(compareBuffers(exchange.getRequestBody(),handler.getAccumulatedRequest())); + } + } + finally + { + server.stop(); + server.join(); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java index 2970de67aa9..adcaab849a5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java @@ -13,6 +13,16 @@ package org.eclipse.jetty.client; +import static org.junit.Assert.assertEquals; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; + public class SocketConnectionTest extends AbstractConnectionTest { protected HttpClient newHttpClient() @@ -21,10 +31,66 @@ public class SocketConnectionTest extends AbstractConnectionTest httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); return httpClient; } - + @Override - public void testServerClosedConnection() + public void testServerClosedConnection() throws Exception { - // TODO work out why this does not work + // Differently from the SelectConnector, the SocketConnector cannot detect server closes. + // Therefore, upon a second send, the exchange will fail. + // Applications needs to retry it explicitly. + + ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(null); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = this.newHttpClient(); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 0\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone()); + + remote.close(); + + exchange.reset(); + httpClient.send(exchange); + + assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone()); + } + finally + { + httpClient.stop(); + } + } + + public void testIdleConnection() throws Exception + { + super.testIdleConnection(); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java new file mode 100644 index 00000000000..a2f041b7d0e --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java @@ -0,0 +1,148 @@ +package org.eclipse.jetty.client; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.io.OutputStream; +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.http.HttpMethods; +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 SimpleProxy proxy; + private SSLServerSocket acceptor; + + @Before + public void init() throws Exception + { + threadPool = Executors.newCachedThreadPool(); + + client = new HttpClient(); + client.setMaxConnectionsPerAddress(1); + client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + File keyStore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory cf = client.getSslContextFactory(); + cf.setKeyStorePath(keyStore.getAbsolutePath()); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + client.start(); + + SSLContext sslContext = cf.getSslContext(); + acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(0); + + int serverPort = acceptor.getLocalPort(); + + proxy = new SimpleProxy(threadPool, "localhost", serverPort); + proxy.start(); + logger.debug(":{} <==> :{}", 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 + { + ContentExchange exchange = new ContentExchange(true); + exchange.setURL("https://localhost:" + proxy.getPort()); + String method = HttpMethods.GET; + exchange.setMethod(method); + client.send(exchange); + Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); + + final SSLSocket server = (SSLSocket)acceptor.accept(); + server.setUseClientMode(false); + + Future handshake = threadPool.submit(new Callable() + { + 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(method)); + 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)); + + Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone()); + Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus()); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java new file mode 100644 index 00000000000..abcdce4d001 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java @@ -0,0 +1,1370 @@ +package org.eclipse.jetty.client; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.SocketTimeoutException; +import java.nio.channels.SocketChannel; +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 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; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SslConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.OS; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; + +public class SslBytesServerTest extends SslBytesTest +{ + private final AtomicInteger sslHandles = new AtomicInteger(); + private final AtomicInteger sslFlushes = new AtomicInteger(); + private final AtomicInteger httpParses = new AtomicInteger(); + private final AtomicReference serverEndPoint = new AtomicReference(); + private final int idleTimeout = 2000; + private ExecutorService threadPool; + private Server server; + private SSLContext sslContext; + private SimpleProxy proxy; + + @Before + public void init() throws Exception + { + threadPool = Executors.newCachedThreadPool(); + server = new Server(); + + SslSelectChannelConnector connector = new SslSelectChannelConnector() + { + @Override + protected SslConnection newSslConnection(AsyncEndPoint endPoint, SSLEngine engine) + { + serverEndPoint.set(endPoint); + return new SslConnection(engine, endPoint) + { + @Override + public Connection handle() throws IOException + { + sslHandles.incrementAndGet(); + return super.handle(); + } + + @Override + protected SslEndPoint newSslEndPoint() + { + return new SslEndPoint() + { + @Override + public int flush(Buffer buffer) throws IOException + { + sslFlushes.incrementAndGet(); + return super.flush(buffer); + } + }; + } + }; + } + + @Override + protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint) + { + return new org.eclipse.jetty.server.AsyncHttpConnection(this, endPoint, getServer()) + { + @Override + protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler) + { + return new HttpParser(requestBuffers, endPoint, requestHandler) + { + @Override + public int parseNext() throws IOException + { + httpParses.incrementAndGet(); + return super.parseNext(); + } + }; + } + }; + } + }; + connector.setMaxIdleTime(idleTimeout); + +// connector.setPort(5870); + connector.setPort(0); + + File keyStore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory cf = connector.getSslContextFactory(); + cf.setKeyStorePath(keyStore.getAbsolutePath()); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + server.addConnector(connector); + server.setHandler(new AbstractHandler() + { + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException + { + request.setHandled(true); + String contentLength = request.getHeader("Content-Length"); + if (contentLength != null) + { + int length = Integer.parseInt(contentLength); + ServletInputStream input = httpRequest.getInputStream(); + ServletOutputStream output = httpResponse.getOutputStream(); + byte[] buffer = new byte[32 * 1024]; + for (int i = 0; i < length; ++i) + { + int read = input.read(buffer); + if ("/echo".equals(target)) + output.write(buffer, 0, read); + } + } + } + }); + server.start(); + int serverPort = connector.getLocalPort(); + + sslContext = cf.getSslContext(); + + proxy = new SimpleProxy(threadPool, "localhost", serverPort); + proxy.start(); + logger.debug(":{} <==> :{}", proxy.getPort(), serverPort); + } + + @After + public void destroy() throws Exception + { + if (proxy != null) + proxy.stop(); + if (server != null) + server.stop(); + if (threadPool != null) + threadPool.shutdownNow(); + } + + @Test + public void testHandshake() throws Exception + { + final SSLSocket client = newClient(); + + Future handshake = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Client Hello + TLSRecord record = proxy.readFromClient(); + Assert.assertNotNull(record); + proxy.flushToServer(record); + + // Server Hello + Certificate + Server Done + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + // Client Key Exchange + record = proxy.readFromClient(); + Assert.assertNotNull(record); + proxy.flushToServer(record); + + // Change Cipher Spec + record = proxy.readFromClient(); + Assert.assertNotNull(record); + proxy.flushToServer(record); + + // Client Done + record = proxy.readFromClient(); + Assert.assertNotNull(record); + proxy.flushToServer(record); + + // Change Cipher Spec + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + // Server Done + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + Assert.assertNull(handshake.get(5, TimeUnit.SECONDS)); + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + closeClient(client); + } + + @Test + public void testHandshakeWithSplitBoundary() throws Exception + { + final SSLSocket client = newClient(); + + Future handshake = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Client Hello + TLSRecord record = proxy.readFromClient(); + byte[] bytes = record.getBytes(); + byte[] chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + byte[] chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // Server Hello + Certificate + Server Done + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Client Key Exchange + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // Change Cipher Spec + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // Client Done + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // Change Cipher Spec + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + // Server Done + record = proxy.readFromServer(); + Assert.assertNotNull(record); + proxy.flushToClient(record); + + Assert.assertNull(handshake.get(5, TimeUnit.SECONDS)); + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + client.close(); + + // Close Alert + record = proxy.readFromClient(); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Close Alert + record = proxy.readFromServer(); + proxy.flushToClient(record); + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } + + @Test + public void testRequestResponse() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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(); + proxy.flushToServer(record); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Application data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + closeClient(client); + } + + @Test + public void testHandshakeAndRequestOneByteAtATime() throws Exception + { + final SSLSocket client = newClient(); + + Future handshake = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Client Hello + TLSRecord record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + + // Server Hello + Certificate + Server Done + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Client Key Exchange + record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + + // Change Cipher Spec + record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + + // Client Done + record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + + // Change Cipher Spec + record = proxy.readFromServer(); + proxy.flushToClient(record); + + // Server Done + record = proxy.readFromServer(); + proxy.flushToClient(record); + + Assert.assertNull(handshake.get(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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 + record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Application data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(750)); + Assert.assertThat(sslFlushes.get(), lessThan(750)); + Assert.assertThat(httpParses.get(), lessThan(150)); + + client.close(); + + // Close Alert + record = proxy.readFromClient(); + for (byte b : record.getBytes()) + proxy.flushToServer(50, b); + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Close Alert + record = proxy.readFromServer(); + proxy.flushToClient(record); + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } + + /** + * TODO + * Currently this test does not pass. + * The problem is a mix of Java not being able to perform SSL half closes + * (but SSL supporting it), and the current implementation in Jetty. + * See the test below, that passes and whose only difference is that we + * delay the output shutdown from the client. + * + * @throws Exception if the test fails + */ + @Ignore + @Test + public void testRequestWithCloseAlertAndShutdown() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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(); + proxy.flushToServer(record); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + client.close(); + + // Close Alert + record = proxy.readFromClient(); + proxy.flushToServer(record); + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Expect response from server + // SSLSocket is limited and we cannot read the response, but we make sure + // it is application data and not a close alert + record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + // Close Alert + record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); + // We can't forward to the client, its socket is already closed + + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } + + @Test + public void testRequestWithCloseAlert() throws Exception + { + if ( !OS.IS_LINUX ) + { + // currently we are ignoring this test on anything other then linux + + //http://tools.ietf.org/html/rfc2246#section-7.2.1 + + // TODO (react to this portion which seems to allow win/mac behavior) + //It is required that the other party respond with a close_notify alert of its own + //and close down the connection immediately, discarding any pending writes. It is not + //required for the initiator of the close to wait for the responding + //close_notify alert before closing the read side of the connection. + return; + } + + + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + client.close(); + + // Close Alert + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); + proxy.flushToServer(record); + + // Do not close the raw socket yet + + // Expect response from server + // SSLSocket is limited and we cannot read the response, but we make sure + // it is application data and not a close alert + record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + // Close Alert + record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); + // We can't forward to the client, its socket is already closed + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } + + @Test + public void testRequestWithRawClose() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Application data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + // Close the raw socket, this generates a truncation attack + proxy.flushToServer((TLSRecord)null); + + // Expect raw close from server + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + client.close(); + } + + @Test + public void testRequestWithBigContentWriteBlockedAndResetException() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + byte[] data = new byte[128 * 1024]; + Arrays.fill(data, (byte)'X'); + final String content = new String(data, "UTF-8"); + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + OutputStream clientOutput = client.getOutputStream(); + clientOutput.write(("" + + "GET /echo HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Length: " + content.length() + "\r\n" + + "\r\n" + + content).getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Nine TLSRecords will be generated for the request + for (int i = 0; i < 9; ++i) + { + // Application data + TLSRecord record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToServer(record, 0); + } + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // We asked the server to echo back the data we sent + // but we do not read it, thus causing a write interest + // on the server. + // However, we then simulate that the client resets the + // connection, and this will cause an exception in the + // server that is trying to write the data + + proxy.resetServer(); + + // Wait a while to detect spinning + TimeUnit.SECONDS.sleep(1); + + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + client.close(); + } + + @Test + public void testRequestWithCloseAlertWithSplitBoundary() throws Exception + { + if ( !OS.IS_LINUX ) + { + // currently we are ignoring this test on anything other then linux + + //http://tools.ietf.org/html/rfc2246#section-7.2.1 + + // TODO (react to this portion which seems to allow win/mac behavior) + //It is required that the other party respond with a close_notify alert of its own + //and close down the connection immediately, discarding any pending writes. It is not + //required for the initiator of the close to wait for the responding + //close_notify alert before closing the read side of the connection. + return; + } + + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + Future request = threadPool.submit(new Callable() + { + 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 dataRecord = proxy.readFromClient(); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + client.close(); + + // Close Alert + TLSRecord closeRecord = proxy.readFromClient(); + + // Send request and half of the close alert bytes + byte[] dataBytes = dataRecord.getBytes(); + byte[] closeBytes = closeRecord.getBytes(); + byte[] bytes = new byte[dataBytes.length + closeBytes.length / 2]; + System.arraycopy(dataBytes, 0, bytes, 0, dataBytes.length); + System.arraycopy(closeBytes, 0, bytes, dataBytes.length, closeBytes.length / 2); + proxy.flushToServer(100, bytes); + + bytes = new byte[closeBytes.length - closeBytes.length / 2]; + System.arraycopy(closeBytes, closeBytes.length / 2, bytes, 0, bytes.length); + proxy.flushToServer(100, bytes); + + // Do not close the raw socket yet + + // Expect response from server + // SSLSocket is limited and we cannot read the response, but we make sure + // it is application data and not a close alert + TLSRecord record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + // Close Alert + record = proxy.readFromServer(); + Assert.assertNotNull(record); + Assert.assertEquals(TLSRecord.Type.ALERT, record.getType()); + // We can't forward to the client, its socket is already closed + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } + + @Test + public void testRequestWithContentWithSplitBoundary() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + final String content = "0123456789ABCDEF"; + + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + OutputStream clientOutput = client.getOutputStream(); + clientOutput.write(("" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: " + content.length() + "\r\n" + + "\r\n" + + content).getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Application data + TLSRecord record = proxy.readFromClient(); + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + byte[] chunk1 = new byte[2 * record.getBytes().length / 3]; + System.arraycopy(record.getBytes(), 0, chunk1, 0, chunk1.length); + proxy.flushToServer(100, chunk1); + + byte[] chunk2 = new byte[record.getBytes().length - chunk1.length]; + System.arraycopy(record.getBytes(), chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk2); + + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + closeClient(client); + } + + @Test + public void testRequestWithBigContentWithSplitBoundary() throws Exception + { + final SSLSocket client = newClient(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Use a content that is larger than the TLS record which is 2^14 (around 16k) + byte[] data = new byte[128 * 1024]; + Arrays.fill(data, (byte)'X'); + final String content = new String(data, "UTF-8"); + + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + OutputStream clientOutput = client.getOutputStream(); + clientOutput.write(("" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: " + content.length() + "\r\n" + + "\r\n" + + content).getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Nine TLSRecords will be generated for the request + for (int i = 0; i < 9; ++i) + { + // Application data + TLSRecord record = proxy.readFromClient(); + byte[] bytes = record.getBytes(); + byte[] chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + byte[] chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(150)); + + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + TLSRecord record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(150)); + + closeClient(client); + } + + @Test + public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception + { + assumeJavaVersionSupportsTLSRenegotiations(); + + final SSLSocket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Use a content that is larger than the TLS record which is 2^14 (around 16k) + byte[] data1 = new byte[80 * 1024]; + Arrays.fill(data1, (byte)'X'); + String content1 = new String(data1, "UTF-8"); + byte[] data2 = new byte[48 * 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 + Future renegotiation = threadPool.submit(new Callable() + { + 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 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); + + // Trigger a read to have the client write the final renegotiation steps + client.setSoTimeout(100); + try + { + client.getInputStream().read(); + Assert.fail(); + } + catch (SocketTimeoutException x) + { + // Expected + } + + // 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)); + + // Write the rest of the request + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + clientOutput.write(content2.getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Three TLSRecords will be generated for the remainder of the content + for (int i = 0; i < 3; ++i) + { + // Application data + record = proxy.readFromClient(); + proxy.flushToServer(record); + } + + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Read response + // Application Data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + closeClient(client); + } + + @Test + public void testRequestWithBigContentWithRenegotiationInMiddleOfContentWithSplitBoundary() throws Exception + { + assumeJavaVersionSupportsTLSRenegotiations(); + + final SSLSocket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Use a content that is larger than the TLS record which is 2^14 (around 16k) + byte[] data1 = new byte[80 * 1024]; + Arrays.fill(data1, (byte)'X'); + String content1 = new String(data1, "UTF-8"); + byte[] data2 = new byte[48 * 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 + Future renegotiation = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + client.startHandshake(); + return null; + } + }); + + // Renegotiation Handshake + TLSRecord record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); + byte[] bytes = record.getBytes(); + byte[] chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + byte[] chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // 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); + + // Trigger a read to have the client write the final renegotiation steps + client.setSoTimeout(100); + try + { + client.getInputStream().read(); + Assert.fail(); + } + catch (SocketTimeoutException x) + { + // Expected + } + + // Renegotiation Change Cipher + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + proxy.flushToServer(100, chunk2); + + // Renegotiation Handshake + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); + bytes = record.getBytes(); + chunk1 = new byte[2 * bytes.length / 3]; + System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); + chunk2 = new byte[bytes.length - chunk1.length]; + System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); + proxy.flushToServer(100, chunk1); + // Do not write the second chunk now, but merge it with content, see below + + Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS)); + + // Write the rest of the request + Future request = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + clientOutput.write(content2.getBytes("UTF-8")); + clientOutput.flush(); + return null; + } + }); + + // Three TLSRecords will be generated for the remainder of the content + // Merge the last chunk of the renegotiation with the first data record + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + byte[] dataBytes = record.getBytes(); + byte[] mergedBytes = new byte[chunk2.length + dataBytes.length]; + System.arraycopy(chunk2, 0, mergedBytes, 0, chunk2.length); + System.arraycopy(dataBytes, 0, mergedBytes, chunk2.length, dataBytes.length); + proxy.flushToServer(100, mergedBytes); + // Write the remaining 2 TLS records + for (int i = 0; i < 2; ++i) + { + // Application data + record = proxy.readFromClient(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToServer(record); + } + + Assert.assertNull(request.get(5, TimeUnit.SECONDS)); + + // Read response + // Application Data + record = proxy.readFromServer(); + Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType()); + proxy.flushToClient(record); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + + // Check that we did not spin + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(100)); + + closeClient(client); + } + + @Test + public void testServerShutdownOutputClientDoesNotCloseServerCloses() throws Exception + { + final SSLSocket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); + client.startHandshake(); + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + byte[] data = new byte[3 * 1024]; + Arrays.fill(data, (byte)'Y'); + String content = new String(data, "UTF-8"); + automaticProxyFlow = proxy.startAutomaticFlow(); + clientOutput.write(("" + + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: " + content.length() + "\r\n" + + "Connection: close\r\n" + + "\r\n" + + content).getBytes("UTF-8")); + clientOutput.flush(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertTrue(line.startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + } + Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); + + // Check client is at EOF + Assert.assertEquals(-1,client.getInputStream().read()); + + // Client should close the socket, but let's hold it open. + + // Check that we did not spin + TimeUnit.MILLISECONDS.sleep(100); + Assert.assertThat(sslHandles.get(), lessThan(20)); + Assert.assertThat(sslFlushes.get(), lessThan(20)); + Assert.assertThat(httpParses.get(), lessThan(50)); + + // The server has shutdown the output since the client sent a Connection: close + // but the client does not close, so the server must idle timeout the endPoint. + + TimeUnit.MILLISECONDS.sleep(idleTimeout + idleTimeout/2); + + Assert.assertFalse(serverEndPoint.get().isOpen()); + } + + private void assumeJavaVersionSupportsTLSRenegotiations() + { + // Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21 + // so we check the java version in order to avoid to fail the test. + String javaVersion = System.getProperty("java.version"); + Pattern regexp = Pattern.compile("1\\.6\\.0_(\\d{2})"); + Matcher matcher = regexp.matcher(javaVersion); + if (matcher.matches()) + { + String nano = matcher.group(1); + Assume.assumeThat(Integer.parseInt(nano), greaterThan(21)); + } + } + + private SSLSocket newClient() throws IOException, InterruptedException + { + SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort()); + client.setUseClientMode(true); + Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); + return client; + } + + private void closeClient(SSLSocket client) throws Exception + { + client.close(); + + // Close Alert + TLSRecord record = proxy.readFromClient(); + proxy.flushToServer(record); + // Socket close + record = proxy.readFromClient(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToServer(record); + + // Close Alert + record = proxy.readFromServer(); + proxy.flushToClient(record); + // Socket close + record = proxy.readFromServer(); + Assert.assertNull(String.valueOf(record), record); + proxy.flushToClient(record); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java new file mode 100644 index 00000000000..4c8266180c4 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java @@ -0,0 +1,353 @@ +package org.eclipse.jetty.client; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.junit.Assert; + +public abstract class SslBytesTest +{ + protected final Logger logger = Log.getLogger(getClass()); + + public static class TLSRecord + { + private final SslBytesServerTest.TLSRecord.Type type; + private final byte[] bytes; + + public TLSRecord(SslBytesServerTest.TLSRecord.Type type, byte[] bytes) + { + this.type = type; + this.bytes = bytes; + } + + public SslBytesServerTest.TLSRecord.Type getType() + { + return type; + } + + public byte[] getBytes() + { + return bytes; + } + + @Override + public String toString() + { + return "TLSRecord [" + type + "] " + bytes.length + " bytes"; + } + + public enum Type + { + CHANGE_CIPHER_SPEC(20), ALERT(21), HANDSHAKE(22), APPLICATION(23); + + private int code; + + private Type(int code) + { + this.code = code; + SslBytesServerTest.TLSRecord.Type.Mapper.codes.put(this.code, this); + } + + public static SslBytesServerTest.TLSRecord.Type from(int code) + { + SslBytesServerTest.TLSRecord.Type result = SslBytesServerTest.TLSRecord.Type.Mapper.codes.get(code); + if (result == null) + throw new IllegalArgumentException("Invalid TLSRecord.Type " + code); + return result; + } + + private static class Mapper + { + private static final Map codes = new HashMap(); + } + } + } + + public class SimpleProxy implements Runnable + { + private final CountDownLatch latch = new CountDownLatch(1); + private final ExecutorService threadPool; + private final String serverHost; + private final int serverPort; + private volatile ServerSocket serverSocket; + private volatile Socket server; + private volatile Socket client; + + public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort) + { + this.threadPool = threadPool; + this.serverHost = serverHost; + this.serverPort = serverPort; + } + + public void start() throws Exception + { +// serverSocket = new ServerSocket(5871); + serverSocket = new ServerSocket(0); + Thread acceptor = new Thread(this); + acceptor.start(); + server = new Socket(serverHost, serverPort); + } + + public void stop() throws Exception + { + serverSocket.close(); + } + + public void run() + { + try + { + client = serverSocket.accept(); + latch.countDown(); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + + public int getPort() + { + return serverSocket.getLocalPort(); + } + + public TLSRecord readFromClient() throws IOException + { + TLSRecord record = read(client); + logger.debug("C --> P {}", record); + return record; + } + + private TLSRecord read(Socket socket) throws IOException + { + InputStream input = socket.getInputStream(); + int first = -2; + while (true) + { + try + { + socket.setSoTimeout(500); + first = input.read(); + break; + } + catch (SocketTimeoutException x) + { + if (Thread.currentThread().isInterrupted()) + break; + } + } + if (first == -2) + throw new InterruptedIOException(); + else if (first == -1) + return null; + + if (first >= 0x80) + { + // SSLv2 Record + int hiLength = first & 0x3F; + int loLength = input.read(); + int length = (hiLength << 8) + loLength; + byte[] bytes = new byte[2 + length]; + bytes[0] = (byte)first; + bytes[1] = (byte)loLength; + return read(TLSRecord.Type.HANDSHAKE, input, bytes, 2, length); + } + else + { + // TLS Record + int major = input.read(); + int minor = input.read(); + int hiLength = input.read(); + int loLength = input.read(); + int length = (hiLength << 8) + loLength; + byte[] bytes = new byte[1 + 2 + 2 + length]; + bytes[0] = (byte)first; + bytes[1] = (byte)major; + bytes[2] = (byte)minor; + bytes[3] = (byte)hiLength; + bytes[4] = (byte)loLength; + return read(TLSRecord.Type.from(first), input, bytes, 5, length); + } + } + + private TLSRecord read(SslBytesServerTest.TLSRecord.Type type, InputStream input, byte[] bytes, int offset, int length) throws IOException + { + while (length > 0) + { + int read = input.read(bytes, offset, length); + if (read < 0) + throw new EOFException(); + offset += read; + length -= read; + } + return new TLSRecord(type, bytes); + } + + public void flushToServer(TLSRecord record) throws Exception + { + flushToServer(record, 100); + } + + public void flushToServer(TLSRecord record, long sleep) throws Exception + { + if (record == null) + { + server.shutdownOutput(); + if (client.isOutputShutdown()) + { + client.close(); + server.close(); + } + } + else + { + flush(sleep, server, record.getBytes()); + } + } + + public void flushToServer(long sleep, byte... bytes) throws Exception + { + flush(sleep, server, bytes); + } + + private void flush(long sleep, Socket socket, byte... bytes) throws Exception + { + OutputStream output = socket.getOutputStream(); + output.write(bytes); + output.flush(); + if (sleep > 0) + TimeUnit.MILLISECONDS.sleep(sleep); + } + + public TLSRecord readFromServer() throws IOException + { + TLSRecord record = read(server); + logger.debug("P <-- S {}", record); + return record; + } + + public void flushToClient(TLSRecord record) throws Exception + { + if (record == null) + { + client.shutdownOutput(); + if (server.isOutputShutdown()) + { + server.close(); + client.close(); + } + } + else + { + flush(0, client, record.getBytes()); + } + } + + public SslBytesServerTest.SimpleProxy.AutomaticFlow startAutomaticFlow() throws InterruptedException + { + final CountDownLatch startLatch = new CountDownLatch(2); + final CountDownLatch stopLatch = new CountDownLatch(2); + Future clientToServer = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + startLatch.countDown(); + logger.debug("Automatic flow C --> S started"); + try + { + while (true) + { + flushToServer(readFromClient(), 0); + } + } + catch (InterruptedIOException x) + { + return null; + } + finally + { + stopLatch.countDown(); + logger.debug("Automatic flow C --> S finished"); + } + } + }); + Future serverToClient = threadPool.submit(new Callable() + { + public Object call() throws Exception + { + startLatch.countDown(); + logger.debug("Automatic flow C <-- S started"); + try + { + while (true) + { + flushToClient(readFromServer()); + } + } + catch (InterruptedIOException x) + { + return null; + } + finally + { + stopLatch.countDown(); + logger.debug("Automatic flow C <-- S finished"); + } + } + }); + Assert.assertTrue(startLatch.await(5, TimeUnit.SECONDS)); + return new SslBytesServerTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient); + } + + public boolean awaitClient(int time, TimeUnit unit) throws InterruptedException + { + return latch.await(time, unit); + } + + public void resetServer() throws IOException + { + // Calling setSoLinger(true, 0) causes close() + // to send a RST instead of a FIN, causing an + // exception to be thrown on the other end + server.setSoLinger(true, 0); + server.close(); + } + + public class AutomaticFlow + { + private final CountDownLatch stopLatch; + private final Future clientToServer; + private final Future serverToClient; + + public AutomaticFlow(CountDownLatch stopLatch, Future clientToServer, Future serverToClient) + { + this.stopLatch = stopLatch; + this.clientToServer = clientToServer; + this.serverToClient = serverToClient; + } + + public boolean stop(long time, TimeUnit unit) throws InterruptedException + { + clientToServer.cancel(true); + serverToClient.cancel(true); + return stopLatch.await(time, unit); + } + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java index aa6b1c5afd6..0b07a42403d 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java @@ -20,9 +20,6 @@ import java.util.Set; import javax.security.auth.Subject; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Credential; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.IdentityService; @@ -38,6 +35,9 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.util.ssl.SslContextFactory; public class SslCertSecuredExchangeTest extends ContentExchangeTest { @@ -58,7 +58,7 @@ public class SslCertSecuredExchangeTest extends ContentExchangeTest cf.setValidateCerts(true); cf.setCrlPath(_crlpath); cf.setNeedClientAuth(true); - cf.setKeyStore(_keypath); + cf.setKeyStorePath(_keypath); cf.setKeyStorePassword(_password); cf.setKeyManagerPassword(_password); cf.setTrustStore(_trustpath); @@ -159,7 +159,7 @@ public class SslCertSecuredExchangeTest extends ContentExchangeTest cf.setCrlPath(_crlpath); cf.setCertAlias("client"); - cf.setKeyStore(_clientpath); + cf.setKeyStorePath(_clientpath); cf.setKeyStorePassword(_password); cf.setKeyManagerPassword(_password); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java index 847bf1fd519..510ca711563 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.client; import java.io.File; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; @@ -24,6 +23,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; public class SslContentExchangeTest extends ContentExchangeTest @@ -37,7 +37,7 @@ public class SslContentExchangeTest SslSelectChannelConnector connector = new SslSelectChannelConnector(); File keystore = MavenTestingUtils.getTestResourceFile("keystore"); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore.getAbsolutePath()); + cf.setKeyStorePath(keystore.getAbsolutePath()); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); cf.setSessionCachingEnabled(true); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java index 6ea97bc5aa4..2493a1b2f5c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java @@ -16,64 +16,31 @@ package org.eclipse.jetty.client; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; -import org.eclipse.jetty.http.ssl.SslContextFactory; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.SslServerAndClientCreator; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ssl.SslSocketConnector; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.toolchain.test.OS; -import org.eclipse.jetty.toolchain.test.Stress; import org.junit.Assume; import org.junit.Before; import org.junit.Test; /** * Functional testing for HttpExchange. - * - * - * */ public class SslHttpExchangeTest extends HttpExchangeTest { + protected static ServerAndClientCreator serverAndClientCreator = new SslServerAndClientCreator(); + /* ------------------------------------------------------------ */ @Before - @Override - public void setUp() throws Exception + public void setUpOnce() throws Exception { _scheme="https"; - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(2000); - _httpClient.setTimeout(2500); - _httpClient.setConnectTimeout(1000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); - _httpClient.setMaxConnectionsPerAddress(2); - _httpClient.start(); + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + Connector[] connectors = _server.getConnectors(); + _port = connectors[0].getLocalPort(); } - /* ------------------------------------------------------------ */ - @Override - protected void newServer() - { - _server = new Server(); - //SslSelectChannelConnector connector = new SslSelectChannelConnector(); - SslSocketConnector connector = new SslSocketConnector(); - - String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); - - connector.setPort(0); - SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore); - cf.setKeyStorePassword("storepwd"); - cf.setKeyManagerPassword("keypwd"); - connector.setAllowRenegotiate(true); - - _server.setConnectors(new Connector[] - { connector }); - _connector=connector; - } - /* ------------------------------------------------------------ */ private void IgnoreTestOnBuggyIBM() { @@ -103,9 +70,6 @@ public class SslHttpExchangeTest extends HttpExchangeTest @Override public void testPerf() throws Exception { - // TODO needs to be further investigated - Assume.assumeTrue(!OS.IS_OSX || Stress.isEnabled()); - // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532 IgnoreTestOnBuggyIBM(); super.testPerf(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java index 4f3787095af..30112af7305 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java @@ -19,8 +19,6 @@ import java.util.HashSet; import java.util.Set; import org.eclipse.jetty.client.security.Realm; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -34,6 +32,8 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.ssl.SslContextFactory; public class SslSecuredContentExchangeTest extends ContentExchangeTest @@ -63,7 +63,7 @@ extends ContentExchangeTest SslSelectChannelConnector connector = new SslSelectChannelConnector(); File keystore = MavenTestingUtils.getTestResourceFile("keystore"); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore.getAbsolutePath()); + cf.setKeyStorePath(keystore.getAbsolutePath()); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); server.addConnector(connector); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java index 4b4c4dcf19f..91c5da40963 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java @@ -20,7 +20,6 @@ import java.util.Set; import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -34,6 +33,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; import org.junit.Test; /* ------------------------------------------------------------ */ diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java index b64faa19d63..1e9c18ead65 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java @@ -34,8 +34,6 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.security.HashRealmResolver; import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.http.HttpMethods; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; @@ -51,6 +49,8 @@ import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -165,7 +165,7 @@ public class SslSecurityListenerTest connector.setPort(0); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore); + cf.setKeyStorePath(keystore); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java index 72a27102f5b..375020a71a1 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java @@ -6,7 +6,6 @@ import java.security.KeyStore; import java.security.cert.CRL; import java.util.Collection; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; @@ -16,6 +15,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.security.CertificateUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; public abstract class SslValidationTestBase extends ContentExchangeTest { @@ -52,7 +52,7 @@ public abstract class SslValidationTestBase extends ContentExchangeTest srvFactory.setCrlPath(_crlpath); srvFactory.setNeedClientAuth(true); - srvFactory.setKeyStore(_keypath); + srvFactory.setKeyStorePath(_keypath); srvFactory.setKeyStorePassword(_password); srvFactory.setKeyManagerPassword(_password); @@ -88,7 +88,7 @@ public abstract class SslValidationTestBase extends ContentExchangeTest cf.setValidateCerts(true); cf.setCrlPath(_crlpath); - cf.setKeyStore(_clientpath); + cf.setKeyStorePath(_clientpath); cf.setKeyStorePassword(_password); cf.setKeyManagerPassword(_password); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java new file mode 100644 index 00000000000..4aefca55148 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java @@ -0,0 +1,420 @@ +package org.eclipse.jetty.client; + +import static org.hamcrest.Matchers.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.nio.channels.SocketChannel; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class TimeoutTest +{ + private static final Logger logger = Log.getLogger(TimeoutTest.class); + private final AtomicInteger httpParses = new AtomicInteger(); + private final AtomicInteger httpRequests = new AtomicInteger(); + private ExecutorService threadPool; + private Server server; + private int serverPort; + private final AtomicReference serverEndPoint = new AtomicReference(); + + @Before + public void init() throws Exception + { + threadPool = Executors.newCachedThreadPool(); + server = new Server(); + + SelectChannelConnector connector = new SelectChannelConnector() + { + @Override + protected AsyncConnection newConnection(SocketChannel channel, final AsyncEndPoint endPoint) + { + serverEndPoint.set(endPoint); + return new org.eclipse.jetty.server.AsyncHttpConnection(this,endPoint,getServer()) + { + @Override + protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler) + { + return new HttpParser(requestBuffers,endPoint,requestHandler) + { + @Override + public int parseNext() throws IOException + { + httpParses.incrementAndGet(); + return super.parseNext(); + } + }; + } + }; + } + }; + connector.setMaxIdleTime(2000); + + // connector.setPort(5870); + connector.setPort(0); + + server.addConnector(connector); + server.setHandler(new AbstractHandler() + { + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, + ServletException + { + httpRequests.incrementAndGet(); + request.setHandled(true); + String contentLength = request.getHeader("Content-Length"); + if (contentLength != null) + { + int length = Integer.parseInt(contentLength); + ServletInputStream input = request.getInputStream(); + for (int i = 0; i < length; ++i) + input.read(); + } + } + }); + server.start(); + serverPort = connector.getLocalPort(); + + httpRequests.set(0); + logger.debug(" => :{}",serverPort); + } + + @After + public void destroy() throws Exception + { + if (server != null) + server.stop(); + if (threadPool != null) + threadPool.shutdownNow(); + } + + private Socket newClient() throws IOException, InterruptedException + { + Socket client = new Socket("localhost",serverPort); + return client; + } + + /** + * Test that performs a normal http POST request, with connection:close. + * Check that shutdownOutput is sufficient to close the server connection. + */ + @Test + public void testServerCloseClientDoesClose() throws Exception + { + // Log.getLogger("").setDebugEnabled(true); + final Socket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + byte[] data = new byte[3 * 1024]; + Arrays.fill(data,(byte)'Y'); + String content = new String(data,"UTF-8"); + + // The request section + StringBuilder req = new StringBuilder(); + req.append("POST / HTTP/1.1\r\n"); + req.append("Host: localhost\r\n"); + req.append("Content-Type: text/plain\r\n"); + req.append("Content-Length: ").append(content.length()).append("\r\n"); + req.append("Connection: close\r\n"); + req.append("\r\n"); + // and now, the POST content section. + req.append(content); + + // Send request to server + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + + InputStream in = null; + InputStreamReader isr = null; + BufferedReader reader = null; + try + { + in = client.getInputStream(); + isr = new InputStreamReader(in); + reader = new BufferedReader(isr); + + // Read the response header + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertThat(line,startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + { + break; + } + } + Assert.assertEquals("one request handled",1,httpRequests.get()); + + Assert.assertEquals("EOF received",-1,client.getInputStream().read()); + + // shutdown the output + client.shutdownOutput(); + + // Check that we did not spin + int httpParseCount = httpParses.get(); + Assert.assertThat(httpParseCount,lessThan(50)); + + // Try to write another request (to prove that stream is closed) + try + { + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + + Assert.fail("Should not have been able to send a second POST request (connection: close)"); + } + catch(SocketException e) + { + } + + Assert.assertEquals("one request handled",1,httpRequests.get()); + } + finally + { + IO.close(reader); + IO.close(isr); + IO.close(in); + closeClient(client); + } + } + + /** + * Test that performs a seemingly normal http POST request, but with + * a client that issues "connection: close", and then attempts to + * write a second POST request. + *

+ * The connection should be closed by the server + */ + @Test + public void testServerCloseClientMoreDataSent() throws Exception + { + // Log.getLogger("").setDebugEnabled(true); + final Socket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + byte[] data = new byte[3 * 1024]; + Arrays.fill(data,(byte)'Y'); + String content = new String(data,"UTF-8"); + + // The request section + StringBuilder req = new StringBuilder(); + req.append("POST / HTTP/1.1\r\n"); + req.append("Host: localhost\r\n"); + req.append("Content-Type: text/plain\r\n"); + req.append("Content-Length: ").append(content.length()).append("\r\n"); + req.append("Connection: close\r\n"); + req.append("\r\n"); + // and now, the POST content section. + req.append(content); + + // Send request to server + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + + InputStream in = null; + InputStreamReader isr = null; + BufferedReader reader = null; + try + { + in = client.getInputStream(); + isr = new InputStreamReader(in); + reader = new BufferedReader(isr); + + // Read the response header + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertThat(line,startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + { + break; + } + } + + Assert.assertEquals("EOF received",-1,client.getInputStream().read()); + Assert.assertEquals("one request handled",1,httpRequests.get()); + + // Don't shutdown the output + // client.shutdownOutput(); + + // server side seeking EOF + Assert.assertTrue("is open",serverEndPoint.get().isOpen()); + Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown()); + Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown()); + + + // Check that we did not spin + TimeUnit.SECONDS.sleep(1); + int httpParseCount = httpParses.get(); + Assert.assertThat(httpParseCount,lessThan(50)); + + + // Write another request (which is ignored as the stream is closing), which causes real close. + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + + // Check that we did not spin + TimeUnit.SECONDS.sleep(1); + httpParseCount = httpParses.get(); + Assert.assertThat(httpParseCount,lessThan(50)); + + + // server side is closed + Assert.assertFalse("is open",serverEndPoint.get().isOpen()); + Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown()); + Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown()); + + Assert.assertEquals("one request handled",1,httpRequests.get()); + + } + finally + { + IO.close(reader); + IO.close(isr); + IO.close(in); + closeClient(client); + } + } + + + /** + * Test that performs a seemingly normal http POST request, but with + * a client that issues "connection: close", and then does not close + * the connection after reading the response. + *

+ * The connection should be closed by the server after a timeout. + */ + @Test + public void testServerCloseClientDoesNotClose() throws Exception + { + // Log.getLogger("").setDebugEnabled(true); + final Socket client = newClient(); + final OutputStream clientOutput = client.getOutputStream(); + + byte[] data = new byte[3 * 1024]; + Arrays.fill(data,(byte)'Y'); + String content = new String(data,"UTF-8"); + + // The request section + StringBuilder req = new StringBuilder(); + req.append("POST / HTTP/1.1\r\n"); + req.append("Host: localhost\r\n"); + req.append("Content-Type: text/plain\r\n"); + req.append("Content-Length: ").append(content.length()).append("\r\n"); + req.append("Connection: close\r\n"); + req.append("\r\n"); + // and now, the POST content section. + req.append(content); + + // Send request to server + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + + InputStream in = null; + InputStreamReader isr = null; + BufferedReader reader = null; + try + { + in = client.getInputStream(); + isr = new InputStreamReader(in); + reader = new BufferedReader(isr); + + // Read the response header + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertThat(line,startsWith("HTTP/1.1 200 ")); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + { + break; + } + } + + Assert.assertEquals("EOF received",-1,client.getInputStream().read()); + Assert.assertEquals("one request handled",1,httpRequests.get()); + + // Don't shutdown the output + // client.shutdownOutput(); + + // server side seeking EOF + Assert.assertTrue("is open",serverEndPoint.get().isOpen()); + Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown()); + Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown()); + + + // Wait for the server idle timeout + TimeUnit.SECONDS.sleep(3); + int httpParseCount = httpParses.get(); + Assert.assertThat(httpParseCount,lessThan(50)); + + // server side is closed + Assert.assertFalse("is open",serverEndPoint.get().isOpen()); + Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown()); + Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown()); + + Assert.assertEquals("one request handled",1,httpRequests.get()); + + + // client will eventually get broken pipe if it keeps writing + try + { + for (int i=0;i<1000;i++) + { + clientOutput.write(req.toString().getBytes("UTF-8")); + clientOutput.flush(); + } + Assert.fail("Client should have seen a broken pipe"); + } + catch(IOException e) + { + // expected broken pipe + } + + } + finally + { + IO.close(reader); + IO.close(isr); + IO.close(in); + closeClient(client); + } + } + + private void closeClient(Socket client) throws IOException + { + client.close(); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java index f4b3d82168c..74dd69d43d4 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.client; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -26,17 +25,17 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import junit.framework.TestCase; - -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.http.HttpMethods; -import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.IO; - +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; /** * Functional testing for HttpExchange. @@ -44,91 +43,71 @@ import org.eclipse.jetty.util.IO; * @author Matthew Purland * @author Greg Wilkins */ -public class UnexpectedDataTest extends TestCase +public class UnexpectedDataTest { - protected int _maxConnectionsPerAddress = 1; - protected String _scheme = "http://"; - protected Server _server; - protected int _port; - protected HttpClient _httpClient; - protected Connector _connector; - protected AtomicInteger _count = new AtomicInteger(); + private Server _server; + private int _port; + private HttpClient _httpClient; + private Connector _connector; + private AtomicInteger _count = new AtomicInteger(); - protected void setUp() throws Exception + @Before + public void setUp() throws Exception { startServer(); - _httpClient=new HttpClient(); + _httpClient = new HttpClient(); _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(_maxConnectionsPerAddress); + _httpClient.setMaxConnectionsPerAddress(1); _httpClient.start(); } - protected void tearDown() throws Exception + @After + public void tearDown() throws Exception { _httpClient.stop(); Thread.sleep(500); stopServer(); } + @Test public void testUnexpectedData() throws Exception { - for (int i=0; i<4; i++) + for (int i = 0; i < 4; ++i) { - final CountDownLatch done=new CountDownLatch(1); - ContentExchange httpExchange=new ContentExchange() + final CountDownLatch done = new CountDownLatch(1); + ContentExchange httpExchange = new ContentExchange() { protected void onResponseComplete() throws IOException { super.onResponseComplete(); - done.countDown(); } }; - httpExchange.setURL(_scheme+"localhost:"+_port+"/?i="+i); + httpExchange.setURL("http://localhost:" + _port + "/?i=" + i); httpExchange.setMethod(HttpMethods.GET); _httpClient.send(httpExchange); - - done.await(1,TimeUnit.SECONDS); - - int status = httpExchange.getStatus(); - String result=httpExchange.getResponseContent(); - assertEquals("i="+i,0,result.indexOf("")); - assertEquals("i="+i,result.length()-10,result.indexOf("")); - assertEquals(HttpExchange.STATUS_COMPLETED, status); - - Thread.sleep(5); - } - } - public static void copyStream(InputStream in, OutputStream out) - { - try - { - byte[] buffer=new byte[1024]; - int len; - while ((len=in.read(buffer))>=0) - { - out.write(buffer,0,len); - } - } - catch (EofException e) - { - System.err.println(e); - } - catch (IOException e) - { - e.printStackTrace(); + Assert.assertTrue(done.await(1000, TimeUnit.SECONDS)); + + int status = httpExchange.getStatus(); + String result = httpExchange.getResponseContent(); + Assert.assertEquals("i=" + i, 0, result.indexOf("")); + Assert.assertEquals("i=" + i, result.length() - 10, result.indexOf("")); + Assert.assertEquals(HttpExchange.STATUS_COMPLETED, status); + + // Give the client the time to read -1 from server before issuing the next request + // There is currently no simple way to be notified of connection closed. + Thread.sleep(500); } } protected void newServer() throws Exception { - _server=new Server(); + _server = new Server(); _server.setGracefulShutdown(500); - _connector=new SelectChannelConnector(); - + _connector = new SelectChannelConnector(); _connector.setPort(0); - _server.setConnectors(new Connector[] { _connector }); + _server.setConnectors(new Connector[]{_connector}); } protected void startServer() throws Exception @@ -137,28 +116,28 @@ public class UnexpectedDataTest extends TestCase _server.setHandler(new AbstractHandler() { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException + throws IOException, ServletException { - int i=0; + int i = 0; try { baseRequest.setHandled(true); response.setStatus(200); _count.incrementAndGet(); - + if (request.getMethod().equalsIgnoreCase("GET")) { - StringBuffer buffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); buffer.append("\r\n"); - for (; i<100; i++) + for (; i < 100; i++) { - buffer.append(" "+i+"\r\n"); + buffer.append(" ").append(i).append("\r\n"); } buffer.append("\r\n"); - + byte[] buff = buffer.toString().getBytes(); response.setContentLength(buff.length); - + buffer.append("extra data"); buff = buffer.toString().getBytes(); @@ -169,35 +148,30 @@ public class UnexpectedDataTest extends TestCase else { response.setContentType(request.getContentType()); - int size=request.getContentLength(); - ByteArrayOutputStream bout = new ByteArrayOutputStream(size>0?size:32768); - IO.copy(request.getInputStream(),bout); + int size = request.getContentLength(); + ByteArrayOutputStream bout = new ByteArrayOutputStream(size > 0 ? size : 32768); + IO.copy(request.getInputStream(), bout); response.getOutputStream().write(bout.toByteArray()); } } - catch(IOException e) + catch (IOException e) { e.printStackTrace(); throw e; } - catch(Throwable e) + catch (Throwable e) { e.printStackTrace(); throw new ServletException(e); } - finally - { - // System.err.println("HANDLED "+i); - } } }); _server.start(); - _port=_connector.getLocalPort(); + _port = _connector.getLocalPort(); } private void stopServer() throws Exception { _server.stop(); } - } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java index 07480e128d3..d282544e8e7 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java @@ -162,8 +162,6 @@ public class WebSocketUpgradeTest int status = httpExchange.waitForDone(); assertEquals(HttpExchange.STATUS_COMPLETED, status); - // System.err.println("results="+_results); - assertEquals("serverWS.onConnect", _results.poll(1,TimeUnit.SECONDS)); TestWebSocket serverWS = (TestWebSocket)_results.poll(1,TimeUnit.SECONDS); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java new file mode 100644 index 00000000000..f7d13cb4c73 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java @@ -0,0 +1,55 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.helperClasses; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSocketConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; + + +/* ------------------------------------------------------------ */ +/** + */ +public abstract class AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + private static final Logger LOG = Log.getLogger(AbstractSslServerAndClientCreator.class); + + /* ------------------------------------------------------------ */ + public Server createServer() throws Exception + { + Server server = new Server(); + // SslSelectChannelConnector connector = new SslSelectChannelConnector(); + SslSocketConnector connector = new SslSocketConnector(); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + + connector.setPort(0); + SslContextFactory cf = connector.getSslContextFactory(); + cf.setKeyStorePath(keystore); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + + server.setConnectors(new Connector[]{ connector }); + server.setHandler(new GenericServerHandler()); + server.start(); + return server; + } + + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java new file mode 100644 index 00000000000..b9627e47dba --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java @@ -0,0 +1,24 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + +public class AsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + /* ------------------------------------------------------------ */ + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + httpClient.getSslContextFactory().setKeyStorePath(keystore); + httpClient.getSslContextFactory().setKeyStorePassword("storepwd"); + httpClient.getSslContextFactory().setKeyManagerPassword("keypwd"); + httpClient.start(); + return httpClient; + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java new file mode 100644 index 00000000000..584f4b1a64b --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java @@ -0,0 +1,27 @@ +package org.eclipse.jetty.client.helperClasses; + +import java.io.FileInputStream; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + +public class ExternalKeyStoreAsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + + httpClient.setKeyStoreInputStream(new FileInputStream(keystore)); + httpClient.setKeyStorePassword("storepwd"); + httpClient.setKeyManagerPassword("keypwd"); + httpClient.start(); + return httpClient; + } + + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java new file mode 100644 index 00000000000..cc9ac055454 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java @@ -0,0 +1,91 @@ +package org.eclipse.jetty.client.helperClasses; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Generic Server Handler used for various client tests. + */ +public class GenericServerHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(GenericServerHandler.class); + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + int i = 0; + try + { + baseRequest.setHandled(true); + response.setStatus(200); + + if (request.getServerName().equals("jetty.eclipse.org")) + { + response.getOutputStream().println("Proxy request: " + request.getRequestURL()); + response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION)); + } + else if (request.getMethod().equalsIgnoreCase("GET")) + { + response.getOutputStream().println(""); + for (; i < 100; i++) + { + response.getOutputStream().println(" " + i + ""); + } + else if (request.getMethod().equalsIgnoreCase("OPTIONS")) + { + if ("*".equals(target)) + { + response.setContentLength(0); + response.setHeader("Allow","GET,HEAD,POST,PUT,DELETE,MOVE,OPTIONS,TRACE"); + } + } + else if (request.getMethod().equalsIgnoreCase("SLEEP")) + { + Thread.sleep(10000); + } + else + { + response.setContentType(request.getContentType()); + int size = request.getContentLength(); + ByteArrayOutputStream bout = new ByteArrayOutputStream(size > 0?size:32768); + IO.copy(request.getInputStream(),bout); + response.getOutputStream().write(bout.toByteArray()); + } + } + catch (InterruptedException e) + { + LOG.debug(e); + } + catch (EofException e) + { + LOG.info(e.toString()); + LOG.debug(e); + throw e; + } + catch (IOException e) + { + LOG.warn(e); + throw e; + } + catch (Throwable e) + { + LOG.warn(e); + throw new ServletException(e); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java new file mode 100644 index 00000000000..d9a2f1d3eef --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java @@ -0,0 +1,36 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +public class HttpServerAndClientCreator implements ServerAndClientCreator +{ + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setIdleTimeout(idleTimeout); + httpClient.setTimeout(timeout); + httpClient.setConnectTimeout(connectTimeout); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + httpClient.start(); + return httpClient; + } + + public Server createServer() throws Exception + { + Server _server = new Server(); + _server.setGracefulShutdown(500); + Connector _connector = new SelectChannelConnector(); + + _connector.setMaxIdleTime(3000000); + + _connector.setPort(0); + _server.setConnectors(new Connector[]{ _connector }); + _server.setHandler(new GenericServerHandler()); + _server.start(); + return _server; + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java new file mode 100644 index 00000000000..015006f6802 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java @@ -0,0 +1,11 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.server.Server; + +public interface ServerAndClientCreator +{ + Server createServer() throws Exception; + + HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception; +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java new file mode 100644 index 00000000000..d0e0ad0a115 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java @@ -0,0 +1,19 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; + +public class SslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setIdleTimeout(idleTimeout); + httpClient.setTimeout(timeout); + httpClient.setConnectTimeout(connectTimeout); + httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); + httpClient.setMaxConnectionsPerAddress(2); + httpClient.start(); + return httpClient; + } +} diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml index 90cbc77114f..6c4c47b893a 100644 --- a/jetty-continuation/pom.xml +++ b/jetty-continuation/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-continuation diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java index 639ae2e1819..e5bd75144a5 100644 --- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java +++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java @@ -1,6 +1,7 @@ package org.eclipse.jetty.continuation; import java.io.IOException; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java index da0c080ea36..142b675ec2d 100644 --- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java +++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java @@ -23,7 +23,7 @@ public class Jetty6Continuation implements ContinuationFilter.FilteredContinuati // Exception reused for all continuations // Turn on debug in ContinuationFilter to see real stack trace. private final static ContinuationThrowable __exception = new ContinuationThrowable(); - + private final ServletRequest _request; private ServletResponse _response; private final org.mortbay.util.ajax.Continuation _j6Continuation; @@ -66,7 +66,7 @@ public class Jetty6Continuation implements ContinuationFilter.FilteredContinuati _j6Continuation.resume(); } } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String) @@ -233,8 +233,6 @@ public class Jetty6Continuation implements ContinuationFilter.FilteredContinuati Throwable th=_retry; _retry=null; - if (th instanceof ThreadDeath) - throw (ThreadDeath)th; if (th instanceof Error) throw (Error)th; if (th instanceof RuntimeException) @@ -245,7 +243,7 @@ public class Jetty6Continuation implements ContinuationFilter.FilteredContinuati for (ContinuationListener l: _listeners) l.onComplete(this); } - + return true; } } diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml index 26dbc516e9f..528e7ce46c0 100644 --- a/jetty-deploy/pom.xml +++ b/jetty-deploy/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-deploy diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml index ebbec509329..dd25f7ff34c 100644 --- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml +++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml @@ -22,7 +22,7 @@ org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern - .*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$ + .*/.*jsp-api-[^/]*\.jar$|.*/.*jsp-[^/]*\.jar$|.*/.*taglibs[^/]*\.jar$ diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index 0cb29ccc7c1..fb8e1a5d36b 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -481,7 +481,7 @@ public class DeploymentManager extends AggregateLifeCycle while (it.hasNext()) { Node node = it.next(); - LOG.debug("Executing Node: " + node); + LOG.debug("Executing Node {}",node); _lifecycle.runBindings(node,appentry.app,this); appentry.setLifeCycleNode(node); } diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java index 81e641cbfdf..6cbb2d6fc39 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java @@ -257,7 +257,7 @@ public class WebAppDeployer extends AbstractLifeCycle if (path != null && path.equals(app.getFile().getCanonicalPath())) { - LOG.debug("Already deployed:"+path); + LOG.debug("Already deployed: {}",path); continue files; } } diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java index 0a5c08c6bdf..c7119dafd33 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java @@ -51,13 +51,14 @@ public class StandardUndeployer implements AppLifeCycle.Binding for (int i = 0, n = children.length; i < n; i++) { Handler child = children[i]; - LOG.debug("Child handler: " + child); + LOG.debug("Child handler {}",child); if (child.equals(context)) { - LOG.debug("Removing handler: " + child); + LOG.debug("Removing handler {}",child); coll.removeHandler(child); child.destroy(); - LOG.debug(String.format("After removal: %d (originally %d)",coll.getHandlers().length,originalCount)); + if (LOG.isDebugEnabled()) + LOG.debug("After removal: {} (originally {})",coll.getHandlers().length,originalCount); } else if (child instanceof HandlerCollection) { diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index 629f13f92ec..8bf03f30a12 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -99,7 +99,8 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A @Override protected void doStart() throws Exception { - if (LOG.isDebugEnabled()) LOG.debug(this.getClass().getSimpleName() + ".doStart()"); + if (LOG.isDebugEnabled()) + LOG.debug(this.getClass().getSimpleName() + ".doStart()"); if (_monitoredDir == null) { throw new IllegalStateException("No configuration dir specified"); @@ -132,7 +133,8 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A /* ------------------------------------------------------------ */ protected void fileAdded(String filename) throws Exception { - if (LOG.isDebugEnabled()) LOG.debug("added ",filename); + if (LOG.isDebugEnabled()) + LOG.debug("added {}",filename); App app = ScanningAppProvider.this.createApp(filename); if (app != null) { @@ -144,7 +146,8 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A /* ------------------------------------------------------------ */ protected void fileChanged(String filename) throws Exception { - if (LOG.isDebugEnabled()) LOG.debug("changed ",filename); + if (LOG.isDebugEnabled()) + LOG.debug("changed {}",filename); App app = _appMap.remove(filename); if (app != null) { @@ -161,7 +164,8 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A /* ------------------------------------------------------------ */ protected void fileRemoved(String filename) throws Exception { - if (LOG.isDebugEnabled()) LOG.debug("removed ",filename); + if (LOG.isDebugEnabled()) + LOG.debug("removed {}",filename); App app = _appMap.remove(filename); if (app != null) _deploymentManager.removeApp(app); diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java index d0611fa6923..f2535c20d33 100644 --- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java @@ -111,6 +111,7 @@ public class ScanningAppProviderRuntimeUpdatesTest jetty.copyWebapp("foo-webapp-1.war","foo.war"); jetty.copyContext("foo.xml","foo.xml"); + waitForDirectoryScan(); waitForDirectoryScan(); jetty.assertWebAppContextsExists("/foo"); @@ -125,12 +126,14 @@ public class ScanningAppProviderRuntimeUpdatesTest jetty.copyWebapp("foo-webapp-1.war","foo.war"); jetty.copyContext("foo.xml","foo.xml"); + waitForDirectoryScan(); waitForDirectoryScan(); jetty.assertWebAppContextsExists("/foo"); jetty.removeContext("foo.xml"); + waitForDirectoryScan(); waitForDirectoryScan(); // FIXME: hot undeploy with removal not working! - jetty.assertNoWebAppContexts(); @@ -151,6 +154,7 @@ public class ScanningAppProviderRuntimeUpdatesTest jetty.copyWebapp("foo-webapp-1.war","foo.war"); jetty.copyContext("foo.xml","foo.xml"); + waitForDirectoryScan(); waitForDirectoryScan(); jetty.assertWebAppContextsExists("/foo"); @@ -165,6 +169,7 @@ public class ScanningAppProviderRuntimeUpdatesTest // This should result in the existing foo.war being replaced with the new foo.war waitForDirectoryScan(); + waitForDirectoryScan(); jetty.assertWebAppContextsExists("/foo"); // Test that webapp response contains "-2" diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index c4aacee9169..173d546d426 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT jetty-distribution Jetty :: Distribution Assemblies @@ -20,8 +20,7 @@ 2.1.0.v201004190952 1.2.0.v201004190952 1.0.0.v201004190952 - - 2.1.3-b10 + 2.1.0.v201110031002 1.2.0.v201004190952 3.1.0.v200803061910 1.1.1.v201004190952 @@ -77,7 +76,7 @@ - + @@ -158,147 +157,6 @@ maven-dependency-plugin - - unpack - generate-resources - - unpack - - - - - org.eclipse.jetty - jetty-rewrite - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-ajp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - test-jetty-webapp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-jmx - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-util - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-webapp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-deploy - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-plus - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-server - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-security - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-policy - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-monitor - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-overlay-deployer - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - - copy generate-resources @@ -317,187 +175,6 @@ ${assembly-directory}/ VERSION.txt - - org.eclipse.jetty - jetty-util - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-io - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-http - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-server - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-security - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-servlet - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-servlets - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-xml - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-webapp - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - - org.eclipse.jetty - jetty-deploy - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-jmx - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-rewrite - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-ajp - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-annotations - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-client - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-jndi - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-policy - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-monitor - ${project.version} - jar - true - ** - ${assembly-directory}/lib/monitor - - - org.eclipse.jetty - jetty-plus - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-continuation - ${project.version} - jar - true - ** - ${assembly-directory}/lib - org.eclipse.jetty test-jetty-webapp @@ -518,45 +195,64 @@ ${assembly-directory} start.jar - - org.eclipse.jetty - jetty-websocket - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-overlay-deployer - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-jsp-2.1 - ${project.version} - jar - true - ** - ${assembly-directory}/lib/jsp - - - org.glassfish.web - jsp-impl - ${central-jsp-version} - jar - true - ** - ${assembly-directory}/lib/jsp - + + copy-lib-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty + jetty-start,jetty-monitor + jar + ${assembly-directory}/lib + + + + copy-lib-monitor-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty + jetty-monitor + jar + true + ${assembly-directory}/lib/monitor + + + + unpack-config-deps + generate-resources + + unpack-dependencies + + + org.eclipse.jetty + config + false + ${assembly-directory} + + + + unpack-javadoc + generate-resources + + unpack-dependencies + + + + org.eclipse.jetty.aggregate + jetty-all + javadoc + true + ${assembly-directory}/javadoc + + @@ -658,6 +354,16 @@ jetty-policy ${project.version} + + org.eclipse.jetty + jetty-servlets + ${project.version} + + + org.eclipse.jetty + jetty-monitor + ${project.version} + org.eclipse.jetty jetty-websocket @@ -669,14 +375,11 @@ ${project.version} - org.eclipse.jetty - jetty-jsp-2.1 + org.eclipse.jetty.aggregate + jetty-all + javadoc + jar ${project.version} - - org.glassfish.web - jsp-impl - ${central-jsp-version} - diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini index 52e0a52d1cc..0ada4c02051 100644 --- a/jetty-distribution/src/main/resources/start.ini +++ b/jetty-distribution/src/main/resources/start.ini @@ -18,9 +18,10 @@ # Below are some recommended options for Sun's JRE #----------------------------------------------------------- # --exec +# -Dorg.apache.jasper.compiler.disablejsr199=true # -Dcom.sun.management.jmxremote # -Dorg.eclipse.jetty.util.log.IGNORED=true -# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true +# -Dorg.eclipse.jetty.LEVEL=DEBUG # -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true # -Xmx2000m # -Xmn512m diff --git a/jetty-jsp-2.1/pom.xml b/jetty-http-spi/pom.xml similarity index 50% rename from jetty-jsp-2.1/pom.xml rename to jetty-http-spi/pom.xml index 7b620307040..856673b86eb 100644 --- a/jetty-jsp-2.1/pom.xml +++ b/jetty-http-spi/pom.xml @@ -2,15 +2,33 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 - jetty-jsp-2.1 - Jetty :: Jetty JSP Additions - Additions to Jasper implementation from Glassfish + jetty-http-spi + Jetty :: Http Service Provider Interface - ${project.groupId}.jsp-2.1 + ${project.groupId}.http.spi + + + junit + junit + test + + + com.sun.net.httpserver + http + 20070405 + provided + + + org.eclipse.jetty + jetty-server + ${project.version} + provided + + @@ -22,30 +40,22 @@ manifest - + - - - - com.sun.org.apache.commons.logging;version="[2.1,3)";glassfish="split", - * - - org.eclipse.jetty*;version="${parsedVersion.osgiVersion}" - org.apache.jasper.glassfish - - <_nouses>true - - - org.apache.maven.plugins maven-jar-plugin + + + artifact-jar + + jar + + + - + ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -54,34 +64,9 @@ org.codehaus.mojo findbugs-maven-plugin - org.eclipse.jetty.jsp.* + org.eclipse.jetty.http.spi.* - - - org.eclipse.jetty - jetty-util - ${project.version} - provided - - - org.mortbay.jetty - jsp-2.1-glassfish - ${javax-servlet-jsp-version} - - - org.mortbay.jetty - servlet-api - - - provided - - - javax.servlet - servlet-api - provided - - diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java new file mode 100644 index 00000000000..7cf80112400 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java @@ -0,0 +1,150 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; + +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.handler.ContextHandler; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Authenticator.Result; +import com.sun.net.httpserver.BasicAuthenticator; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpPrincipal; + +/** + * Jetty handler that bridges requests to {@link HttpHandler}. + */ +public class HttpSpiContextHandler extends ContextHandler +{ + + private HttpContext _httpContext; + + private HttpHandler _httpHandler; + + public HttpSpiContextHandler(HttpContext httpContext, HttpHandler httpHandler) + { + this._httpContext = httpContext; + this._httpHandler = httpHandler; + } + + @Override + public void doScope(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException + { + if (!target.startsWith(getContextPath())) + { + return; + } + + HttpExchange jettyHttpExchange; + if (baseRequest.isSecure()) + { + jettyHttpExchange = new JettyHttpsExchange(_httpContext,req,resp); + } + else + { + jettyHttpExchange = new JettyHttpExchange(_httpContext,req,resp); + } + + // TODO: add filters processing + + try + { + Authenticator auth = _httpContext.getAuthenticator(); + if (auth != null) + { + handleAuthentication(resp,jettyHttpExchange,auth); + } + else + { + _httpHandler.handle(jettyHttpExchange); + } + } + catch (Exception ex) + { + PrintWriter writer = new PrintWriter(jettyHttpExchange.getResponseBody()); + + resp.setStatus(500); + writer.println("

HTTP ERROR: 500

"); + writer.println("
INTERNAL_SERVER_ERROR
"); + writer.println("

RequestURI=" + req.getRequestURI() + "

"); + + writer.println("
");
+            ex.printStackTrace(writer);
+            writer.println("
"); + + writer.println("

Powered by jetty://

"); + + writer.close(); + } + finally + { + baseRequest.setHandled(true); + } + + } + + private void handleAuthentication(HttpServletResponse resp, HttpExchange httpExchange, Authenticator auth) throws IOException + { + Result result = auth.authenticate(httpExchange); + if (result instanceof Authenticator.Failure) + { + int rc = ((Authenticator.Failure)result).getResponseCode(); + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) + { + for (String value : header.getValue()) + resp.addHeader(header.getKey(),value); + } + resp.sendError(rc); + } + else if (result instanceof Authenticator.Retry) + { + int rc = ((Authenticator.Retry)result).getResponseCode(); + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) + { + for (String value : header.getValue()) + resp.addHeader(header.getKey(),value); + } + resp.setStatus(rc); + resp.flushBuffer(); + } + else if (result instanceof Authenticator.Success) + { + HttpPrincipal principal = ((Authenticator.Success)result).getPrincipal(); + ((JettyExchange)httpExchange).setPrincipal(principal); + _httpHandler.handle(httpExchange); + } + } + + public HttpHandler getHttpHandler() + { + return _httpHandler; + } + + public void setHttpHandler(HttpHandler handler) + { + this._httpHandler = handler; + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java new file mode 100644 index 00000000000..f448c118185 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import com.sun.net.httpserver.HttpPrincipal; + +/* ------------------------------------------------------------ */ +/** + */ +public interface JettyExchange +{ + + HttpPrincipal getPrincipal(); + + void setPrincipal(HttpPrincipal principal); + +} \ No newline at end of file diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java new file mode 100644 index 00000000000..b5c2737c67a --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java @@ -0,0 +1,106 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** + * Jetty implementation of {@link com.sun.net.httpserver.HttpContext} + */ +public class JettyHttpContext extends com.sun.net.httpserver.HttpContext +{ + + private HttpSpiContextHandler _jettyContextHandler; + + private HttpServer _server; + + private Map _attributes = new HashMap(); + + private List _filters = new ArrayList(); + + private Authenticator _authenticator; + + + protected JettyHttpContext(HttpServer server, String path, + HttpHandler handler) + { + this._server = server; + _jettyContextHandler = new HttpSpiContextHandler(this, handler); + _jettyContextHandler.setContextPath(path); + } + + protected HttpSpiContextHandler getJettyContextHandler() + { + return _jettyContextHandler; + } + + @Override + public HttpHandler getHandler() + { + return _jettyContextHandler.getHttpHandler(); + } + + @Override + public void setHandler(HttpHandler h) + { + _jettyContextHandler.setHttpHandler(h); + } + + @Override + public String getPath() + { + return _jettyContextHandler.getContextPath(); + } + + @Override + public HttpServer getServer() + { + return _server; + } + + @Override + public Map getAttributes() + { + return _attributes; + } + + @Override + public List getFilters() + { + return _filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) + { + Authenticator previous = _authenticator; + _authenticator = auth; + return previous; + } + + @Override + public Authenticator getAuthenticator() + { + return _authenticator; + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java new file mode 100644 index 00000000000..41809d036e3 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java @@ -0,0 +1,252 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/* ------------------------------------------------------------ */ +/** + */ +public class JettyHttpExchange extends HttpExchange implements JettyExchange +{ + private JettyHttpExchangeDelegate _delegate; + + public JettyHttpExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + super(); + _delegate = new JettyHttpExchangeDelegate(jaxWsContext,req,resp); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#hashCode() + */ + @Override + public int hashCode() + { + return _delegate.hashCode(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestHeaders() + */ + @Override + public Headers getRequestHeaders() + { + return _delegate.getRequestHeaders(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseHeaders() + */ + @Override + public Headers getResponseHeaders() + { + return _delegate.getResponseHeaders(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestURI() + */ + @Override + public URI getRequestURI() + { + return _delegate.getRequestURI(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestMethod() + */ + @Override + public String getRequestMethod() + { + return _delegate.getRequestMethod(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getHttpContext() + */ + @Override + public HttpContext getHttpContext() + { + return _delegate.getHttpContext(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#close() + */ + @Override + public void close() + { + _delegate.close(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + return _delegate.equals(obj); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestBody() + */ + @Override + public InputStream getRequestBody() + { + return _delegate.getRequestBody(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseBody() + */ + @Override + public OutputStream getResponseBody() + { + return _delegate.getResponseBody(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#sendResponseHeaders(int, long) + */ + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + _delegate.sendResponseHeaders(rCode,responseLength); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRemoteAddress() + */ + @Override + public InetSocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseCode() + */ + @Override + public int getResponseCode() + { + return _delegate.getResponseCode(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getLocalAddress() + */ + @Override + public InetSocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getProtocol() + */ + @Override + public String getProtocol() + { + return _delegate.getProtocol(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getAttribute(java.lang.String) + */ + @Override + public Object getAttribute(String name) + { + return _delegate.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setAttribute(java.lang.String, java.lang.Object) + */ + @Override + public void setAttribute(String name, Object value) + { + _delegate.setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setStreams(java.io.InputStream, java.io.OutputStream) + */ + @Override + public void setStreams(InputStream i, OutputStream o) + { + _delegate.setStreams(i,o); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getPrincipal() + */ + @Override + public HttpPrincipal getPrincipal() + { + return _delegate.getPrincipal(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setPrincipal(com.sun.net.httpserver.HttpPrincipal) + */ + public void setPrincipal(HttpPrincipal principal) + { + _delegate.setPrincipal(principal); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#toString() + */ + @Override + public String toString() + { + return _delegate.toString(); + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java new file mode 100644 index 00000000000..b772afbe598 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java @@ -0,0 +1,228 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/** + * Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} + */ +public class JettyHttpExchangeDelegate extends HttpExchange +{ + + private HttpContext _httpContext; + + private HttpServletRequest _req; + + private HttpServletResponse _resp; + + private Headers _responseHeaders = new Headers(); + + private int _responseCode = 0; + + private InputStream _is; + + private OutputStream _os; + + private HttpPrincipal _httpPrincipal; + + JettyHttpExchangeDelegate(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + this._httpContext = jaxWsContext; + this._req = req; + this._resp = resp; + try + { + this._is = req.getInputStream(); + this._os = resp.getOutputStream(); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public Headers getRequestHeaders() + { + Headers headers = new Headers(); + Enumeration en = _req.getHeaderNames(); + while (en.hasMoreElements()) + { + String name = (String)en.nextElement(); + Enumeration en2 = _req.getHeaders(name); + while (en2.hasMoreElements()) + { + String value = (String)en2.nextElement(); + headers.add(name,value); + } + } + return headers; + } + + @Override + public Headers getResponseHeaders() + { + return _responseHeaders; + } + + @Override + public URI getRequestURI() + { + try + { + String uriAsString = _req.getRequestURI(); + if (_req.getQueryString() != null) + { + uriAsString += "?" + _req.getQueryString(); + } + + return new URI(uriAsString); + } + catch (URISyntaxException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public String getRequestMethod() + { + return _req.getMethod(); + } + + @Override + public HttpContext getHttpContext() + { + return _httpContext; + } + + @Override + public void close() + { + try + { + _resp.getOutputStream().close(); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public InputStream getRequestBody() + { + return _is; + } + + @Override + public OutputStream getResponseBody() + { + return _os; + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + this._responseCode = rCode; + + for (Map.Entry> stringListEntry : _responseHeaders.entrySet()) + { + String name = stringListEntry.getKey(); + List values = stringListEntry.getValue(); + + for (String value : values) + { + _resp.setHeader(name,value); + } + } + if (responseLength > 0) + { + _resp.setHeader("content-length","" + responseLength); + } + _resp.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() + { + return new InetSocketAddress(_req.getRemoteAddr(),_req.getRemotePort()); + } + + @Override + public int getResponseCode() + { + return _responseCode; + } + + @Override + public InetSocketAddress getLocalAddress() + { + return new InetSocketAddress(_req.getLocalAddr(),_req.getLocalPort()); + } + + @Override + public String getProtocol() + { + return _req.getProtocol(); + } + + @Override + public Object getAttribute(String name) + { + return _req.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) + { + _req.setAttribute(name,value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) + { + _is = i; + _os = o; + } + + @Override + public HttpPrincipal getPrincipal() + { + return _httpPrincipal; + } + + public void setPrincipal(HttpPrincipal principal) + { + this._httpPrincipal = principal; + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java new file mode 100644 index 00000000000..931daec057c --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java @@ -0,0 +1,262 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; + + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Jetty implementation of {@link com.sun.net.httpserver.HttpServer}. + */ +public class JettyHttpServer extends com.sun.net.httpserver.HttpServer +{ + private static final Logger LOG = Log.getLogger(JettyHttpServer.class); + + + private Server _server; + + private boolean _serverShared; + + private InetSocketAddress _addr; + + private ThreadPoolExecutor _executor; + + private Map _contexts = new HashMap(); + + private Map _connectors = new HashMap(); + + + public JettyHttpServer(Server server, boolean shared) + { + this._server = server; + this._serverShared = shared; + } + + @Override + public void bind(InetSocketAddress addr, int backlog) throws IOException + { + // check if there is already a connector listening + Connector[] connectors = _server.getConnectors(); + if (connectors != null) + { + for (Connector connector : connectors) + { + if (connector.getPort() == addr.getPort()) { + if (LOG.isDebugEnabled()) LOG.debug("server already bound to port " + addr.getPort() + ", no need to rebind"); + return; + } + } + } + + if (_serverShared) + throw new IOException("jetty server is not bound to port " + addr.getPort()); + + this._addr = addr; + + if (LOG.isDebugEnabled()) LOG.debug("binding server to port " + addr.getPort()); + SelectChannelConnector connector = new SelectChannelConnector(); + connector.setAcceptors(1); + connector.setPort(addr.getPort()); + connector.setHost(addr.getHostName()); + _server.addConnector(connector); + + _connectors.put(addr.getHostName() + addr.getPort(), connector); + } + + @Override + public InetSocketAddress getAddress() + { + return _addr; + } + + @Override + public void start() + { + if (_serverShared) return; + + try + { + _server.start(); + } + catch (Exception ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public void setExecutor(Executor executor) + { + if (executor == null) + throw new IllegalArgumentException("missing required 'executor' argument"); + + if (!(executor instanceof ThreadPoolExecutor)) + throw new IllegalArgumentException("only java.util.concurrent.ThreadPoolExecutor instances are allowed, got: " + executor.getClass().getName()); + + if (LOG.isDebugEnabled()) LOG.debug("using ThreadPoolExecutor for server thread pool"); + this._executor = (ThreadPoolExecutor) executor; + _server.setThreadPool(new ThreadPoolExecutorAdapter(_executor)); + } + + @Override + public Executor getExecutor() + { + return _executor; + } + + @Override + public void stop(int delay) + { + cleanUpContexts(); + cleanUpConnectors(); + + if (_serverShared) return; + + try + { + _server.stop(); + } + catch (Exception ex) + { + throw new RuntimeException(ex); + } + } + + private void cleanUpContexts() + { + for (Map.Entry stringJettyHttpContextEntry : _contexts.entrySet()) + { + JettyHttpContext context = stringJettyHttpContextEntry.getValue(); + _server.removeBean(context.getJettyContextHandler()); + } + _contexts.clear(); + } + + private void cleanUpConnectors() + { + for (Map.Entry stringConnectorEntry : _connectors.entrySet()) + { + Connector connector = stringConnectorEntry.getValue(); + try + { + connector.stop(); + } catch (Exception ex) { + LOG.warn(ex); + } + _server.removeConnector(connector); + } + _connectors.clear(); + } + + @Override + public HttpContext createContext(String path, HttpHandler httpHandler) + { + checkIfContextIsFree(path); + + JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); + HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler(); + + ContextHandlerCollection chc = findContextHandlerCollection(_server.getHandlers()); + if (chc == null) + throw new RuntimeException("could not find ContextHandlerCollection, you must configure one"); + + chc.addHandler(jettyContextHandler); + _contexts.put(path, context); + + return context; + } + + private ContextHandlerCollection findContextHandlerCollection(Handler[] handlers) + { + if (handlers == null) + return null; + + for (Handler handler : handlers) + { + if (handler instanceof ContextHandlerCollection) + { + return (ContextHandlerCollection) handler; + } + + if (handler instanceof HandlerCollection) + { + HandlerCollection hc = (HandlerCollection) handler; + ContextHandlerCollection chc = findContextHandlerCollection(hc.getHandlers()); + if (chc != null) + return chc; + } + } + return null; + } + + private void checkIfContextIsFree(String path) + { + Handler serverHandler = _server.getHandler(); + if (serverHandler instanceof ContextHandler) + { + ContextHandler ctx = (ContextHandler) serverHandler; + if (ctx.getContextPath().equals(path)) + throw new RuntimeException("another context already bound to path " + path); + } + + Handler[] handlers = _server.getHandlers(); + if (handlers == null) return; + + for (Handler handler : handlers) + { + if (handler instanceof ContextHandler) { + ContextHandler ctx = (ContextHandler) handler; + if (ctx.getContextPath().equals(path)) + throw new RuntimeException("another context already bound to path " + path); + } + } + } + + @Override + public HttpContext createContext(String path) + { + return createContext(path, null); + } + + @Override + public void removeContext(String path) throws IllegalArgumentException + { + JettyHttpContext context = _contexts.remove(path); + if (context == null) return; + _server.removeBean(context.getJettyContextHandler()); + } + + @Override + public void removeContext(HttpContext context) + { + removeContext(context.getPath()); + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java new file mode 100644 index 00000000000..a141d8b5266 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java @@ -0,0 +1,72 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; + +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsServer; +import com.sun.net.httpserver.spi.HttpServerProvider; + +/** + * Jetty implementation of Java HTTP Server SPI + */ +public class JettyHttpServerProvider extends HttpServerProvider +{ + + private static Server _server; + + public static void setServer(Server server) + { + _server = server; + } + + @Override + public HttpServer createHttpServer(InetSocketAddress addr, int backlog) + throws IOException + { + Server server = _server; + boolean shared = true; + + if (server == null) + { + server = new Server(); + + HandlerCollection handlerCollection = new HandlerCollection(); + handlerCollection.setHandlers(new Handler[] {new ContextHandlerCollection(), new DefaultHandler()}); + server.setHandler(handlerCollection); + + shared = false; + } + + JettyHttpServer jettyHttpServer = new JettyHttpServer(server, shared); + jettyHttpServer.bind(addr, backlog); + return jettyHttpServer; + } + + @Override + public HttpsServer createHttpsServer(InetSocketAddress addr, int backlog) throws IOException + { + throw new UnsupportedOperationException(); + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java new file mode 100644 index 00000000000..f4af2b99ce9 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java @@ -0,0 +1,179 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.net.ssl.SSLSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +/* ------------------------------------------------------------ */ +/** + */ +public class JettyHttpsExchange extends HttpsExchange implements JettyExchange +{ + private JettyHttpExchangeDelegate _delegate; + + public JettyHttpsExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + super(); + _delegate = new JettyHttpExchangeDelegate(jaxWsContext,req,resp); + } + + @Override + public int hashCode() + { + return _delegate.hashCode(); + } + + @Override + public Headers getRequestHeaders() + { + return _delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() + { + return _delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() + { + return _delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() + { + return _delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() + { + return _delegate.getHttpContext(); + } + + @Override + public void close() + { + _delegate.close(); + } + + @Override + public boolean equals(Object obj) + { + return _delegate.equals(obj); + } + + @Override + public InputStream getRequestBody() + { + return _delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() + { + return _delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + _delegate.sendResponseHeaders(rCode,responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() + { + return _delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + @Override + public String getProtocol() + { + return _delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) + { + return _delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) + { + _delegate.setAttribute(name,value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) + { + _delegate.setStreams(i,o); + } + + @Override + public HttpPrincipal getPrincipal() + { + return _delegate.getPrincipal(); + } + + public void setPrincipal(HttpPrincipal principal) + { + _delegate.setPrincipal(principal); + } + + @Override + public String toString() + { + return _delegate.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @see com.sun.net.httpserver.HttpsExchange#getSSLSession() + */ + @Override + public SSLSession getSSLSession() + { + return null; + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java new file mode 100644 index 00000000000..557ed78470c --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java @@ -0,0 +1,119 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; + +import org.eclipse.jetty.util.thread.ThreadPool; + +/** + * Jetty {@link ThreadPool} that bridges requests to a {@link ThreadPoolExecutor}. + */ +public class ThreadPoolExecutorAdapter extends AbstractLifeCycle implements ThreadPool +{ + private static final Logger LOG = Log.getLogger(ThreadPoolExecutorAdapter.class); + + + private ThreadPoolExecutor executor; + + public ThreadPoolExecutorAdapter(ThreadPoolExecutor executor) + { + this.executor = executor; + } + + public boolean dispatch(Runnable job) + { + try + { + executor.execute(job); + return true; + } + catch(RejectedExecutionException e) + { + LOG.warn(e); + return false; + } + } + + public int getIdleThreads() + { + return executor.getPoolSize()-executor.getActiveCount(); + } + + public int getThreads() + { + return executor.getPoolSize(); + } + + public boolean isLowOnThreads() + { + return executor.getActiveCount()>=executor.getMaximumPoolSize(); + } + + public void join() throws InterruptedException + { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + + + + public boolean isFailed() + { + return false; + } + + public boolean isRunning() + { + return !executor.isTerminated() && !executor.isTerminating(); + } + + public boolean isStarted() + { + return !executor.isTerminated() && !executor.isTerminating(); + } + + public boolean isStarting() + { + return false; + } + + public boolean isStopped() + { + return executor.isTerminated(); + } + + public boolean isStopping() + { + return executor.isTerminating(); + } + + protected void doStart() throws Exception + { + if (executor.isTerminated() || executor.isTerminating() || executor.isShutdown()) + throw new IllegalStateException("Cannot restart"); + } + + protected void doStop() throws Exception + { + executor.shutdown(); + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) + executor.shutdownNow(); + } + +} diff --git a/jetty-http-spi/src/main/resources/META-INF/services/com.sun.net.httpserver.spi.HttpServerProvider b/jetty-http-spi/src/main/resources/META-INF/services/com.sun.net.httpserver.spi.HttpServerProvider new file mode 100644 index 00000000000..51958d50ce6 --- /dev/null +++ b/jetty-http-spi/src/main/resources/META-INF/services/com.sun.net.httpserver.spi.HttpServerProvider @@ -0,0 +1 @@ +org.eclipse.jetty.http.spi.JettyHttpServerProvider diff --git a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java new file mode 100644 index 00000000000..27110811a61 --- /dev/null +++ b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java @@ -0,0 +1,71 @@ +package org.eclipse.jetty.http.spi; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.sun.net.httpserver.BasicAuthenticator; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + + +public class TestSPIServer +{ + public static void main(String[] args) throws Exception + { + String host="localhost"; + int port = 8080; + + HttpServer server = new JettyHttpServerProvider().createHttpServer(new + InetSocketAddress(host, port), 10); + server.start(); + + final HttpContext httpContext = server.createContext("/", + new HttpHandler() + { + + @Override + public void handle(HttpExchange exchange) throws IOException + { + Headers responseHeaders = exchange.getResponseHeaders(); + responseHeaders.set("Content-Type","text/plain"); + exchange.sendResponseHeaders(200,0); + + OutputStream responseBody = exchange.getResponseBody(); + Headers requestHeaders = exchange.getRequestHeaders(); + Set keySet = requestHeaders.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) + { + String key = iter.next(); + List values = requestHeaders.get(key); + String s = key + " = " + values.toString() + "\n"; + responseBody.write(s.getBytes()); + } + responseBody.close(); + + } + }); + + httpContext.setAuthenticator(new BasicAuthenticator("Test") + { + @Override + public boolean checkCredentials(String username, String password) + { + if ("username".equals(username) && password.equals("password")) + return true; + return false; + } + }); + + + Thread.sleep(10000000); + + } +} diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml index 4c0c3d5f475..53d5c6e9641 100644 --- a/jetty-http/pom.xml +++ b/jetty-http/pom.xml @@ -2,7 +2,7 @@ jetty-project org.eclipse.jetty - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java index 50f58ae63f1..53df952595a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; @@ -27,11 +27,11 @@ import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * Abstract Generator. Builds HTTP Messages. - * + * * Currently this class uses a system parameter "jetty.direct.writers" to control - * two optional writer to byte conversions. buffer.writers=true will probably be + * two optional writer to byte conversions. buffer.writers=true will probably be * faster, but will consume more memory. This option is just for testing and tuning. - * + * */ public abstract class AbstractGenerator implements Generator { @@ -42,16 +42,16 @@ public abstract class AbstractGenerator implements Generator public final static int STATE_CONTENT = 2; public final static int STATE_FLUSHING = 3; public final static int STATE_END = 4; - + public static final byte[] NO_BYTES = {}; // data protected final Buffers _buffers; // source of buffers protected final EndPoint _endp; - + protected int _state = STATE_HEADER; - + protected int _status = 0; protected int _version = HttpVersions.HTTP_1_1_ORDINAL; protected Buffer _reason; @@ -64,20 +64,20 @@ public abstract class AbstractGenerator implements Generator protected boolean _head = false; protected boolean _noContent = false; protected Boolean _persistent = null; - + protected Buffer _header; // Buffer for HTTP header (and maybe small _content) protected Buffer _buffer; // Buffer for copy of passed _content protected Buffer _content; // Buffer passed to addContent - + protected Buffer _date; - + private boolean _sendServerVersion; - + /* ------------------------------------------------------------------------------- */ /** * Constructor. - * + * * @param buffers buffer pool * @param io the end point */ @@ -89,18 +89,18 @@ public abstract class AbstractGenerator implements Generator /* ------------------------------------------------------------------------------- */ public abstract boolean isRequest(); - + /* ------------------------------------------------------------------------------- */ public abstract boolean isResponse(); - + /* ------------------------------------------------------------------------------- */ public boolean isOpen() { return _endp.isOpen(); } - + /* ------------------------------------------------------------------------------- */ - public void reset(boolean returnBuffers) + public void reset() { _state = STATE_HEADER; _status = 0; @@ -114,27 +114,13 @@ public abstract class AbstractGenerator implements Generator _contentLength = HttpTokens.UNKNOWN_CONTENT; _date = null; - // always return the body buffer - if (_buffer!=null) - _buffers.returnBuffer(_buffer); - _buffer=null; - - if (returnBuffers) - { - if (_header!=null) - _buffers.returnBuffer(_header); - _header=null; - } - else if (_header != null) - _header.clear(); - _content = null; _method=null; } /* ------------------------------------------------------------------------------- */ public void returnBuffers() - { + { if (_buffer!=null && _buffer.length()==0) { _buffers.returnBuffer(_buffer); @@ -145,22 +131,22 @@ public abstract class AbstractGenerator implements Generator { _buffers.returnBuffer(_header); _header=null; - } + } } - + /* ------------------------------------------------------------------------------- */ public void resetBuffer() - { + { if(_state>=STATE_FLUSHING) throw new IllegalStateException("Flushed"); - + _last = false; _persistent=null; _contentWritten = 0; _contentLength = HttpTokens.UNKNOWN_CONTENT; _content=null; if (_buffer!=null) - _buffer.clear(); + _buffer.clear(); } /* ------------------------------------------------------------ */ @@ -190,25 +176,25 @@ public abstract class AbstractGenerator implements Generator _buffer = nb; } } - - /* ------------------------------------------------------------ */ + + /* ------------------------------------------------------------ */ public Buffer getUncheckedBuffer() { return _buffer; } - - /* ------------------------------------------------------------ */ + + /* ------------------------------------------------------------ */ public boolean getSendServerVersion () { return _sendServerVersion; } - - /* ------------------------------------------------------------ */ + + /* ------------------------------------------------------------ */ public void setSendServerVersion (boolean sendServerVersion) { _sendServerVersion = sendServerVersion; } - + /* ------------------------------------------------------------ */ public int getState() { @@ -256,7 +242,7 @@ public abstract class AbstractGenerator implements Generator else _contentLength=value; } - + /* ------------------------------------------------------------ */ /** * @param head The head to set. @@ -277,7 +263,7 @@ public abstract class AbstractGenerator implements Generator ?_persistent.booleanValue() :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL); } - + /* ------------------------------------------------------------ */ public void setPersistent(boolean persistent) { @@ -291,7 +277,7 @@ public abstract class AbstractGenerator implements Generator */ public void setVersion(int version) { - if (_state != STATE_HEADER) + if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START "+_state); _version = version; if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null) @@ -303,7 +289,7 @@ public abstract class AbstractGenerator implements Generator { return _version; } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer) @@ -340,7 +326,7 @@ public abstract class AbstractGenerator implements Generator if (reason!=null) { int len=reason.length(); - + // TODO don't hard code if (len>1024) len=1024; @@ -378,14 +364,14 @@ public abstract class AbstractGenerator implements Generator if(_buffer!=null) _buffer.clear(); } - else + else { _contentWritten+=_buffer.length(); if (_head) _buffer.clear(); } } - + /* ------------------------------------------------------------ */ public boolean isBufferFull() { @@ -404,20 +390,20 @@ public abstract class AbstractGenerator implements Generator { return _contentWritten>0; } - + /* ------------------------------------------------------------ */ public boolean isAllContentWritten() { return _contentLength>=0 && _contentWritten>=_contentLength; } - + /* ------------------------------------------------------------ */ public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException; - + /* ------------------------------------------------------------ */ /** * Complete the message. - * + * * @throws IOException */ public void complete() throws IOException @@ -436,9 +422,9 @@ public abstract class AbstractGenerator implements Generator } /* ------------------------------------------------------------ */ - public abstract long flushBuffer() throws IOException; + public abstract int flushBuffer() throws IOException; + - /* ------------------------------------------------------------ */ public void flush(long maxIdleTime) throws IOException { @@ -450,29 +436,20 @@ public abstract class AbstractGenerator implements Generator if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull()) { flushBuffer(); - + while (now0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()&& !_endp.isOutputShutdown()) { blockForOutput(end-now); now=System.currentTimeMillis(); } } - - // make sure buffered data is also flushed - while (now__cacheSize) __cache.clear(); - __cache.putIfAbsent(value,buffer); + Buffer b=__cache.putIfAbsent(value,buffer); + if (b!=null) + buffer=b; } return buffer; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index d83cb890bec..89926a77ca0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -125,9 +125,26 @@ public class HttpGenerator extends AbstractGenerator /* ------------------------------------------------------------------------------- */ @Override - public void reset(boolean returnBuffers) + public void reset() { - super.reset(returnBuffers); + if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown()) + { + try + { + _endp.shutdownOutput(); + } + catch(IOException e) + { + LOG.ignore(e); + } + } + super.reset(); + if (_buffer!=null) + _buffer.clear(); + if (_header!=null) + _header.clear(); + if (_content!=null) + _content=null; _bypass = false; _needCRLF = false; _needEOC = false; @@ -137,8 +154,6 @@ public class HttpGenerator extends AbstractGenerator _noContent=false; } - - /* ------------------------------------------------------------ */ /** * Add content. @@ -157,7 +172,7 @@ public class HttpGenerator extends AbstractGenerator if (_last || _state==STATE_END) { - LOG.debug("Ignoring extra content {}",content); + LOG.warn("Ignoring extra content {}",content); content.clear(); return; } @@ -166,7 +181,7 @@ public class HttpGenerator extends AbstractGenerator // Handle any unfinished business? if (_content!=null && _content.length()>0 || _bufferChunked) { - if (!_endp.isOpen()) + if (_endp.isOutputShutdown()) throw new EofException(); flushBuffer(); if (_content != null && _content.length()>0) @@ -242,7 +257,7 @@ public class HttpGenerator extends AbstractGenerator if (_last || _state==STATE_END) { - LOG.debug("Ignoring extra content {}",Byte.valueOf(b)); + LOG.warn("Ignoring extra content {}",Byte.valueOf(b)); return false; } @@ -399,7 +414,7 @@ public class HttpGenerator extends AbstractGenerator _contentLength = HttpTokens.NO_CONTENT; _header.put(_method); _header.put((byte)' '); - _header.put(_uri.getBytes("utf-8")); // TODO WRONG! + _header.put(_uri.getBytes("UTF-8")); // TODO check _header.put(HttpTokens.CRLF); _state = STATE_FLUSHING; _noContent=true; @@ -409,7 +424,7 @@ public class HttpGenerator extends AbstractGenerator { _header.put(_method); _header.put((byte)' '); - _header.put(_uri.getBytes("utf-8")); // TODO WRONG! + _header.put(_uri.getBytes("UTF-8")); // TODO check _header.put((byte)' '); _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER); _header.put(HttpTokens.CRLF); @@ -418,7 +433,6 @@ public class HttpGenerator extends AbstractGenerator else { // Responses - if (_version == HttpVersions.HTTP_0_9_ORDINAL) { _persistent = false; @@ -677,7 +691,7 @@ public class HttpGenerator extends AbstractGenerator { // we have seen all the _content there is _contentLength = _contentWritten; - if (content_length == null && (isResponse() || _contentLength>0 || content_type )) + if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent) { // known length but not actually set. _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER); @@ -776,7 +790,6 @@ public class HttpGenerator extends AbstractGenerator // end the header. _header.put(HttpTokens.CRLF); - _state = STATE_CONTENT; } @@ -786,8 +799,6 @@ public class HttpGenerator extends AbstractGenerator } } - - /* ------------------------------------------------------------ */ /** * Complete the message. @@ -814,10 +825,11 @@ public class HttpGenerator extends AbstractGenerator /* ------------------------------------------------------------ */ @Override - public long flushBuffer() throws IOException + public int flushBuffer() throws IOException { try { + if (_state == STATE_HEADER) throw new IllegalStateException("State==HEADER"); @@ -837,79 +849,87 @@ public class HttpGenerator extends AbstractGenerator int total= 0; int len = -1; - int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0); - switch (to_flush) + int to_flush = flushMask(); + int last_flush; + + do { - case 7: - throw new IllegalStateException(); // should never happen! - case 6: - len = _endp.flush(_header, _buffer, null); - break; - case 5: - len = _endp.flush(_header, _content, null); - break; - case 4: - len = _endp.flush(_header); - break; - case 3: - len = _endp.flush(_buffer, _content, null); - break; - case 2: - len = _endp.flush(_buffer); - break; - case 1: - len = _endp.flush(_content); - break; - case 0: + last_flush=to_flush; + switch (to_flush) { - // Nothing more we can write now. - if (_header != null) - _header.clear(); - - _bypass = false; - _bufferChunked = false; - - if (_buffer != null) + case 7: + throw new IllegalStateException(); // should never happen! + case 6: + len = _endp.flush(_header, _buffer, null); + break; + case 5: + len = _endp.flush(_header, _content, null); + break; + case 4: + len = _endp.flush(_header); + break; + case 3: + len = _endp.flush(_buffer, _content, null); + break; + case 2: + len = _endp.flush(_buffer); + break; + case 1: + len = _endp.flush(_content); + break; + case 0: { - _buffer.clear(); - if (_contentLength == HttpTokens.CHUNKED_CONTENT) - { - // reserve some space for the chunk header - _buffer.setPutIndex(CHUNK_SPACE); - _buffer.setGetIndex(CHUNK_SPACE); + len=0; + // Nothing more we can write now. + if (_header != null) + _header.clear(); - // Special case handling for small left over buffer from - // an addContent that caused a buffer flush. - if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + _bypass = false; + _bufferChunked = false; + + if (_buffer != null) + { + _buffer.clear(); + if (_contentLength == HttpTokens.CHUNKED_CONTENT) { - _buffer.put(_content); - _content.clear(); - _content=null; + // reserve some space for the chunk header + _buffer.setPutIndex(CHUNK_SPACE); + _buffer.setGetIndex(CHUNK_SPACE); + + // Special case handling for small left over buffer from + // an addContent that caused a buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + _buffer.put(_content); + _content.clear(); + _content=null; + } } } + + // Are we completely finished for now? + if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + + if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null) + _endp.shutdownOutput(); + } + else + // Try to prepare more to write. + prepareBuffers(); } - // Are we completely finished for now? - if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0)) - { - if (_state == STATE_FLUSHING) - { - _state = STATE_END; - } - - if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null) - { - _endp.shutdownOutput(); - } - } - else - // Try to prepare more to write. - prepareBuffers(); } - } - if (len > 0) - total+=len; + if (len > 0) + total+=len; + + to_flush = flushMask(); + } + // loop while progress is being made (OR we have prepared some buffers that might make progress) + while (len>0 || (to_flush!=0 && last_flush==0)); return total; } @@ -920,6 +940,14 @@ public class HttpGenerator extends AbstractGenerator } } + /* ------------------------------------------------------------ */ + private int flushMask() + { + return ((_header != null && _header.length() > 0)?4:0) + | ((_buffer != null && _buffer.length() > 0)?2:0) + | ((_bypass && _content != null && _content.length() > 0)?1:0); + } + /* ------------------------------------------------------------ */ private void prepareBuffers() { @@ -934,16 +962,19 @@ public class HttpGenerator extends AbstractGenerator if (_content.length() == 0) _content = null; } - + // Chunk buffer if need be if (_contentLength == HttpTokens.CHUNKED_CONTENT) { - if ((_buffer==null||_buffer.length()==0) && _content!=null) + if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null) { // this is a bypass write int size = _content.length(); _bufferChunked = true; + if (_header == null) + _header = _buffers.getHeader(); + // if we need CRLF add this to header if (_needCRLF) { @@ -954,7 +985,7 @@ public class HttpGenerator extends AbstractGenerator // Add the chunk size to the header BufferUtil.putHexInt(_header, size); _header.put(HttpTokens.CRLF); - + // Need a CRLF after the content _needCRLF=true; } @@ -984,7 +1015,10 @@ public class HttpGenerator extends AbstractGenerator } else { - // No space so lets use the header buffer. + // No space so lets use a header buffer. + if (_header == null) + _header = _buffers.getHeader(); + if (_needCRLF) { if (_header.length() > 0) throw new IllegalStateException("EOC"); @@ -1067,9 +1101,11 @@ public class HttpGenerator extends AbstractGenerator @Override public String toString() { - return "HttpGenerator s="+_state+ - " h="+(_header==null?"null":_header.length())+ - " b="+(_buffer==null?"null":_buffer.length())+ - " c="+(_content==null?"null":_content.length()); + return String.format("%s{s=%d,h=%d,b=%d,c=%d}", + getClass().getSimpleName(), + _state, + _header == null ? -1 : _header.length(), + _buffer == null ? -1 : _buffer.length(), + _content == null ? -1 : _content.length()); } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java index b4e77338cda..ec933743bbf 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java @@ -13,14 +13,9 @@ package org.eclipse.jetty.http; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.LineNumberReader; - import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferCache; import org.eclipse.jetty.io.ByteArrayBuffer; -import org.eclipse.jetty.util.log.Log; /** * Cached HTTP Header values. diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index aecc18b069c..f7327177407 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; @@ -54,6 +54,7 @@ public class HttpParser implements Parser public static final int STATE_CHUNK_SIZE=4; public static final int STATE_CHUNK_PARAMS=5; public static final int STATE_CHUNK=6; + public static final int STATE_SEEKING_EOF=7; private final EventHandler _handler; private final Buffers _buffers; // source of buffers @@ -67,7 +68,8 @@ public class HttpParser implements Parser private String _multiLineValue; private int _responseStatus; // If >0 then we are parsing a response private boolean _forceContentBuffer; - + private boolean _persistent; + /* ------------------------------------------------------------------------------- */ protected final View _contentView=new View(); // View of the content in the buffer for {@link Input} protected int _state=STATE_START; @@ -78,7 +80,7 @@ public class HttpParser implements Parser protected int _chunkLength; protected int _chunkPosition; private boolean _headResponse; - + /* ------------------------------------------------------------------------------- */ /** * Constructor. @@ -103,7 +105,7 @@ public class HttpParser implements Parser /* ------------------------------------------------------------------------------- */ /** * Constructor. - * @param buffers the buffers to use + * @param buffers the buffers to use * @param endp the endpoint * @param handler the even handler */ @@ -134,7 +136,7 @@ public class HttpParser implements Parser { _headResponse=head; } - + /* ------------------------------------------------------------------------------- */ public int getState() { @@ -170,7 +172,7 @@ public class HttpParser implements Parser { return isState(STATE_END); } - + /* ------------------------------------------------------------ */ public boolean isMoreInBuffer() throws IOException @@ -185,6 +187,20 @@ public class HttpParser implements Parser return _state == state; } + /* ------------------------------------------------------------------------------- */ + public boolean isPersistent() + { + return _persistent; + } + + /* ------------------------------------------------------------------------------- */ + public void setPersistent(boolean persistent) + { + _persistent = persistent; + if (_state==STATE_END) + _state=STATE_SEEKING_EOF; + } + /* ------------------------------------------------------------------------------- */ /** * Parse until {@link #STATE_END END} state. @@ -203,28 +219,25 @@ public class HttpParser implements Parser if (parseNext()<0) return; } - + /* ------------------------------------------------------------------------------- */ /** * Parse until END state. - * This method will parse any remaining content in the current buffer. It does not care about the + * This method will parse any remaining content in the current buffer. It does not care about the * {@link #getState current state} of the parser. * @see #parse * @see #parseNext */ - public int parseAvailable() throws IOException + public boolean parseAvailable() throws IOException { - int progress = parseNext(); - int total=progress>0?1:0; - + boolean progress=parseNext()>0; + // continue parsing while (!isComplete() && _buffer!=null && _buffer.length()>0) { - progress = parseNext(); - if (progress>0) - total++; + progress |= parseNext()>0; } - return total; + return progress; } @@ -235,187 +248,182 @@ public class HttpParser implements Parser */ public int parseNext() throws IOException { - int progress=0; + try + { + int progress=0; - if (_state == STATE_END) - return 0; - - if (_buffer==null) - { - if (_header == null) + if (_state == STATE_END) + return 0; + + if (_buffer==null) { - _header=_buffers.getHeader(); - } - _buffer=_header; - _tok0=new View.CaseInsensitive(_header); - _tok1=new View.CaseInsensitive(_header); - _tok0.setPutIndex(_tok0.getIndex()); - _tok1.setPutIndex(_tok1.getIndex()); - } - - - if (_state == STATE_CONTENT && _contentPosition == _contentLength) - { - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; - } - - int length=_buffer.length(); - - // Fill buffer if we can - if (length == 0) - { - long filled=fill(); - - if (filled < 0) - { - if (_headResponse && _state>STATE_END) + if (_header == null) { - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; + _header=_buffers.getHeader(); } - if ( _state == STATE_EOF_CONTENT) + _buffer=_header; + _tok0=new View.CaseInsensitive(_header); + _tok1=new View.CaseInsensitive(_header); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + } + + + if (_state == STATE_CONTENT && _contentPosition == _contentLength) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + returnBuffers(); + return 1; + } + + int length=_buffer.length(); + + // Fill buffer if we can + if (length == 0) + { + int filled=-1; + IOException ex=null; + try { - if (_buffer.length()>0) - { - // TODO should we do this here or fall down to main loop? - Buffer chunk=_buffer.get(_buffer.length()); - _contentPosition += chunk.length(); - _contentView.update(chunk); - _handler.content(chunk); // May recurse here - } - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; + filled=fill(); + LOG.debug("filled {}/{}",filled,_buffer.length()); + } + catch(IOException e) + { + LOG.debug(this.toString(),e); + ex=e; } - - return -1; - } - length=_buffer.length(); - } - - // EventHandler header - byte ch; - byte[] array=_buffer.array(); - int last=_state; - while (_state0) - { - if (last!=_state) - { - progress++; - last=_state; - } - - ch=_buffer.get(); - - if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) - { - _eol=HttpTokens.LINE_FEED; - continue; - } - _eol=0; - - switch (_state) - { - case STATE_START: - _contentLength=HttpTokens.UNKNOWN_CONTENT; - _cached=null; - if (ch > HttpTokens.SPACE || ch<0) - { - _buffer.mark(); - _state=STATE_FIELD0; - } - break; + if (filled > 0 ) + progress++; + else if (filled < 0 ) + { + _persistent=false; - case STATE_FIELD0: - if (ch == HttpTokens.SPACE) + // do we have content to deliver? + if (_state>STATE_END) { - _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1); - _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0; - _state=STATE_SPACE1; - continue; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - break; - - case STATE_SPACE1: - if (ch > HttpTokens.SPACE || ch<0) - { - _buffer.mark(); - if (_responseStatus>=0) + if (_buffer.length()>0 && !_headResponse) { - _state=STATE_STATUS; - _responseStatus=ch-'0'; + Buffer chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here } - else - _state=STATE_URI; } - else if (ch < HttpTokens.SPACE) - { - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - break; - case STATE_STATUS: - if (ch == HttpTokens.SPACE) + // was this unexpected? + switch(_state) { - _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); - _state=STATE_SPACE2; - continue; - } - else if (ch>='0' && ch<='9') - { - _responseStatus=_responseStatus*10+(ch-'0'); - continue; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); - _eol=ch; - _state=STATE_HEADER; - _tok0.setPutIndex(_tok0.getIndex()); - _tok1.setPutIndex(_tok1.getIndex()); - _multiLineValue=null; - continue; - } - // not a digit, so must be a URI - _state=STATE_URI; - _responseStatus=-1; - break; + case STATE_END: + case STATE_SEEKING_EOF: + _state=STATE_END; + break; - case STATE_URI: - if (ch == HttpTokens.SPACE) - { - _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); - _state=STATE_SPACE2; - continue; - } - else if (ch < HttpTokens.SPACE && ch>=0) - { - // HTTP/0.9 - _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null); - _state=STATE_END; - _handler.headerComplete(); - _handler.messageComplete(_contentPosition); - return 1; - } - break; + case STATE_EOF_CONTENT: + _state=STATE_END; + _handler.messageComplete(_contentPosition); + break; - case STATE_SPACE2: - if (ch > HttpTokens.SPACE || ch<0) - { - _buffer.mark(); - _state=STATE_FIELD2; + default: + _state=STATE_END; + if (!_headResponse) + _handler.earlyEOF(); + _handler.messageComplete(_contentPosition); } - else if (ch < HttpTokens.SPACE) - { - if (_responseStatus>0) + + if (ex!=null) + throw ex; + + if (!isComplete() && !isIdle()) + throw new EofException(); + + returnBuffers(); + return -1; + } + length=_buffer.length(); + } + + + // Handle header states + byte ch; + byte[] array=_buffer.array(); + int last=_state; + while (_state0) + { + if (last!=_state) + { + progress++; + last=_state; + } + + ch=_buffer.get(); + + if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) + { + _eol=HttpTokens.LINE_FEED; + continue; + } + _eol=0; + + switch (_state) + { + case STATE_START: + _contentLength=HttpTokens.UNKNOWN_CONTENT; + _cached=null; + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD0; + } + break; + + case STATE_FIELD0: + if (ch == HttpTokens.SPACE) + { + _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0; + _state=STATE_SPACE1; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_SPACE1: + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + if (_responseStatus>=0) + { + _state=STATE_STATUS; + _responseStatus=ch-'0'; + } + else + _state=STATE_URI; + } + else if (ch < HttpTokens.SPACE) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_STATUS: + if (ch == HttpTokens.SPACE) + { + _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE2; + continue; + } + else if (ch>='0' && ch<='9') + { + _responseStatus=_responseStatus*10+(ch-'0'); + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) { _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); _eol=ch; @@ -423,473 +431,585 @@ public class HttpParser implements Parser _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; + continue; } - else + // not a digit, so must be a URI + _state=STATE_URI; + _responseStatus=-1; + break; + + case STATE_URI: + if (ch == HttpTokens.SPACE) + { + _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE2; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) { // HTTP/0.9 - _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null); - _state=STATE_END; + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null); + _persistent=false; + _state=STATE_SEEKING_EOF; _handler.headerComplete(); _handler.messageComplete(_contentPosition); + returnBuffers(); return 1; } - } - break; + break; - case STATE_FIELD2: - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - if (_responseStatus>0) - _handler.startResponse(HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark()); - else - _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, HttpVersions.CACHE.lookup(_buffer.sliceFromMark())); - _eol=ch; - _state=STATE_HEADER; - _tok0.setPutIndex(_tok0.getIndex()); - _tok1.setPutIndex(_tok1.getIndex()); - _multiLineValue=null; - continue; - } - break; - - case STATE_HEADER: - switch(ch) - { - case HttpTokens.COLON: - case HttpTokens.SPACE: - case HttpTokens.TAB: + case STATE_SPACE2: + if (ch > HttpTokens.SPACE || ch<0) { - // header value without name - continuation? - _length=-1; - _state=STATE_HEADER_VALUE; - break; + _buffer.mark(); + _state=STATE_FIELD2; } - - default: + else if (ch < HttpTokens.SPACE) { - // handler last header if any - if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) + if (_responseStatus>0) { - - Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); - _cached=null; - Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue); - - int ho=HttpHeaders.CACHE.getOrdinal(header); - if (ho >= 0) - { - int vo; - - switch (ho) - { - case HttpHeaders.CONTENT_LENGTH_ORDINAL: - if (_contentLength != HttpTokens.CHUNKED_CONTENT && _responseStatus!=304 && _responseStatus!=204 && (_responseStatus<100 || _responseStatus>=200)) - { - try - { - _contentLength=BufferUtil.toLong(value); - } - catch(NumberFormatException e) - { - LOG.ignore(e); - throw new HttpException(HttpStatus.BAD_REQUEST_400); - } - if (_contentLength <= 0) - _contentLength=HttpTokens.NO_CONTENT; - } - break; - - case HttpHeaders.TRANSFER_ENCODING_ORDINAL: - value=HttpHeaderValues.CACHE.lookup(value); - vo=HttpHeaderValues.CACHE.getOrdinal(value); - if (HttpHeaderValues.CHUNKED_ORDINAL == vo) - _contentLength=HttpTokens.CHUNKED_CONTENT; - else - { - String c=value.toString(StringUtil.__ISO_8859_1); - if (c.endsWith(HttpHeaderValues.CHUNKED)) - _contentLength=HttpTokens.CHUNKED_CONTENT; - - else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) - throw new HttpException(400,null); - } - break; - } - } - - _handler.parsedHeader(header, value); + _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); + _eol=ch; + _state=STATE_HEADER; _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; } - _buffer.setMarkIndex(-1); - - - // now handle ch - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + else { - // work out the _content demarcation - if (_contentLength == HttpTokens.UNKNOWN_CONTENT) - { - if (_responseStatus == 0 // request - || _responseStatus == 304 // not-modified response - || _responseStatus == 204 // no-content response - || _responseStatus < 200) // 1xx response - _contentLength=HttpTokens.NO_CONTENT; - else - _contentLength=HttpTokens.EOF_CONTENT; - } + // HTTP/0.9 + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null); + _persistent=false; + _state=STATE_SEEKING_EOF; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + returnBuffers(); + return 1; + } + } + break; - _contentPosition=0; + case STATE_FIELD2: + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + Buffer version; + if (_responseStatus>0) + _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark()); + else + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark())); + _eol=ch; + _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL; + _state=STATE_HEADER; + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + continue; + } + break; + + case STATE_HEADER: + switch(ch) + { + case HttpTokens.COLON: + case HttpTokens.SPACE: + case HttpTokens.TAB: + { + // header value without name - continuation? + _length=-1; + _state=STATE_HEADER_VALUE; + break; + } + + default: + { + // handler last header if any + if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) + { + Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); + _cached=null; + Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue); + + int ho=HttpHeaders.CACHE.getOrdinal(header); + if (ho >= 0) + { + int vo; + + switch (ho) + { + case HttpHeaders.CONTENT_LENGTH_ORDINAL: + if (_contentLength != HttpTokens.CHUNKED_CONTENT && _responseStatus!=304 && _responseStatus!=204 && (_responseStatus<100 || _responseStatus>=200)) + { + try + { + _contentLength=BufferUtil.toLong(value); + } + catch(NumberFormatException e) + { + LOG.ignore(e); + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + if (_contentLength <= 0) + _contentLength=HttpTokens.NO_CONTENT; + } + break; + + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: + value=HttpHeaderValues.CACHE.lookup(value); + vo=HttpHeaderValues.CACHE.getOrdinal(value); + if (HttpHeaderValues.CHUNKED_ORDINAL == vo) + _contentLength=HttpTokens.CHUNKED_CONTENT; + else + { + String c=value.toString(StringUtil.__ISO_8859_1); + if (c.endsWith(HttpHeaderValues.CHUNKED)) + _contentLength=HttpTokens.CHUNKED_CONTENT; + + else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) + throw new HttpException(400,null); + } + break; + + case HttpHeaders.CONNECTION_ORDINAL: + switch(HttpHeaderValues.CACHE.getOrdinal(value)) + { + case HttpHeaderValues.CLOSE_ORDINAL: + _persistent=false; + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + _persistent=true; + break; + + case -1: // No match, may be multi valued + { + for (String v : value.toString().split(",")) + { + switch(HttpHeaderValues.CACHE.getOrdinal(v.trim())) + { + case HttpHeaderValues.CLOSE_ORDINAL: + _persistent=false; + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + _persistent=true; + break; + } + } + break; + } + } + } + } + + _handler.parsedHeader(header, value); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + } + _buffer.setMarkIndex(-1); + + + // now handle ch + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + // work out the _content demarcation + if (_contentLength == HttpTokens.UNKNOWN_CONTENT) + { + if (_responseStatus == 0 // request + || _responseStatus == 304 // not-modified response + || _responseStatus == 204 // no-content response + || _responseStatus < 200) // 1xx response + _contentLength=HttpTokens.NO_CONTENT; + else + _contentLength=HttpTokens.EOF_CONTENT; + } + + _contentPosition=0; + _eol=ch; + if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) + _eol=_buffer.get(); + + // We convert _contentLength to an int for this switch statement because + // we don't care about the amount of data available just whether there is some. + switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) + { + case HttpTokens.EOF_CONTENT: + _state=STATE_EOF_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.CHUNKED_CONTENT: + _state=STATE_CHUNKED_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.NO_CONTENT: + _handler.headerComplete(); + _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + returnBuffers(); + return 1; + + default: + _state=STATE_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + } + return 1; + } + else + { + // New header + _length=1; + _buffer.mark(); + _state=STATE_HEADER_NAME; + + // try cached name! + if (array!=null) + { + _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1); + + if (_cached!=null) + { + _length=_cached.length(); + _buffer.setGetIndex(_buffer.markIndex()+_length); + length=_buffer.length(); + } + } + } + } + } + + break; + + case STATE_HEADER_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + _cached=null; + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_NAME; + } + } + + break; + + case STATE_HEADER_IN_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_NAME; + break; + default: + { + _cached=null; + _length++; + } + } + break; + + case STATE_HEADER_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_VALUE; + } + } + break; + + case STATE_HEADER_IN_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_VALUE; + break; + default: + _length++; + } + break; + } + } // end of HEADER states loop + + // ========================== + + // Handle HEAD response + if (_responseStatus>0 && _headResponse) + { + _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentLength); + } + + + // ========================== + + // Handle _content + length=_buffer.length(); + Buffer chunk; + last=_state; + while (_state > STATE_END && length > 0) + { + if (last!=_state) + { + progress++; + last=_state; + } + + if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) + { + _eol=_buffer.get(); + length=_buffer.length(); + continue; + } + _eol=0; + switch (_state) + { + case STATE_EOF_CONTENT: + chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return 1; + + case STATE_CONTENT: + { + long remaining=_contentLength - _contentPosition; + if (remaining == 0) + { + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + returnBuffers(); + return 1; + } + + if (length > remaining) + { + // We can cast reamining to an int as we know that it is smaller than + // or equal to length which is already an int. + length=(int)remaining; + } + + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + + if(_contentPosition == _contentLength) + { + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + returnBuffers(); + } + // TODO adjust the _buffer to keep unconsumed content + return 1; + } + + case STATE_CHUNKED_CONTENT: + { + ch=_buffer.peek(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + _eol=_buffer.get(); + else if (ch <= HttpTokens.SPACE) + _buffer.get(); + else + { + _chunkLength=0; + _chunkPosition=0; + _state=STATE_CHUNK_SIZE; + } + break; + } + + case STATE_CHUNK_SIZE: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + + if (_chunkLength == 0) + { if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) _eol=_buffer.get(); - - // We convert _contentLength to an int for this switch statement because - // we don't care about the amount of data available just whether there is some. - switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) - { - case HttpTokens.EOF_CONTENT: - _state=STATE_EOF_CONTENT; - _handler.headerComplete(); // May recurse here ! - break; - - case HttpTokens.CHUNKED_CONTENT: - _state=STATE_CHUNKED_CONTENT; - _handler.headerComplete(); // May recurse here ! - break; - - case HttpTokens.NO_CONTENT: - _state=STATE_END; - returnBuffers(); - _handler.headerComplete(); - _handler.messageComplete(_contentPosition); - break; - - default: - _state=STATE_CONTENT; - _handler.headerComplete(); // May recurse here ! - break; - } + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + returnBuffers(); return 1; } else - { - // New header - _length=1; - _buffer.mark(); - _state=STATE_HEADER_NAME; - - // try cached name! - if (array!=null) - { - _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1); - - if (_cached!=null) - { - _length=_cached.length(); - _buffer.setGetIndex(_buffer.markIndex()+_length); - length=_buffer.length(); - } - } - } - } - } - - break; - - case STATE_HEADER_NAME: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - if (_length > 0) - _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _eol=ch; - _state=STATE_HEADER; - break; - case HttpTokens.COLON: - if (_length > 0 && _cached==null) - _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _length=-1; - _state=STATE_HEADER_VALUE; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - break; - default: - { - _cached=null; - if (_length == -1) - _buffer.mark(); - _length=_buffer.getIndex() - _buffer.markIndex(); - _state=STATE_HEADER_IN_NAME; - } - } - - break; - - case STATE_HEADER_IN_NAME: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - if (_length > 0) - _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _eol=ch; - _state=STATE_HEADER; - break; - case HttpTokens.COLON: - if (_length > 0 && _cached==null) - _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _length=-1; - _state=STATE_HEADER_VALUE; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - _state=STATE_HEADER_NAME; - break; - default: - { - _cached=null; - _length++; - } - } - break; - - case STATE_HEADER_VALUE: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - if (_length > 0) - { - if (_tok1.length() == 0) - _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); - else - { - // Continuation line! - if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); - _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); - } - } - _eol=ch; - _state=STATE_HEADER; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - break; - default: - { - if (_length == -1) - _buffer.mark(); - _length=_buffer.getIndex() - _buffer.markIndex(); - _state=STATE_HEADER_IN_VALUE; - } - } - break; - - case STATE_HEADER_IN_VALUE: - switch(ch) - { - case HttpTokens.CARRIAGE_RETURN: - case HttpTokens.LINE_FEED: - if (_length > 0) - { - if (_tok1.length() == 0) - _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); - else - { - // Continuation line! - if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); - _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); - _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); - } - } - _eol=ch; - _state=STATE_HEADER; - break; - case HttpTokens.SPACE: - case HttpTokens.TAB: - _state=STATE_HEADER_VALUE; - break; - default: - _length++; - } - break; - } - } // end of HEADER states loop - - // ========================== - - // Handle HEAD response - if (_responseStatus>0 && _headResponse) - { - _state=STATE_END; - _handler.messageComplete(_contentLength); - } - - // ========================== - - // Handle _content - length=_buffer.length(); - Buffer chunk; - last=_state; - while (_state > STATE_END && length > 0) - { - if (last!=_state) - { - progress++; - last=_state; - } - - if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) - { - _eol=_buffer.get(); - length=_buffer.length(); - continue; - } - _eol=0; - switch (_state) - { - case STATE_EOF_CONTENT: - chunk=_buffer.get(_buffer.length()); - _contentPosition += chunk.length(); - _contentView.update(chunk); - _handler.content(chunk); // May recurse here - // TODO adjust the _buffer to keep unconsumed content - return 1; - - case STATE_CONTENT: - { - long remaining=_contentLength - _contentPosition; - if (remaining == 0) - { - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; - } - - if (length > remaining) - { - // We can cast reamining to an int as we know that it is smaller than - // or equal to length which is already an int. - length=(int)remaining; - } - - chunk=_buffer.get(length); - _contentPosition += chunk.length(); - _contentView.update(chunk); - _handler.content(chunk); // May recurse here - - if(_contentPosition == _contentLength) - { - _state=STATE_END; - _handler.messageComplete(_contentPosition); - } - // TODO adjust the _buffer to keep unconsumed content - return 1; - } - - case STATE_CHUNKED_CONTENT: - { - ch=_buffer.peek(); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - _eol=_buffer.get(); - else if (ch <= HttpTokens.SPACE) - _buffer.get(); - else - { - _chunkLength=0; - _chunkPosition=0; - _state=STATE_CHUNK_SIZE; - } - break; - } - - case STATE_CHUNK_SIZE: - { - ch=_buffer.get(); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - _eol=ch; - - if (_chunkLength == 0) - { - if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) - _eol=_buffer.get(); - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; + _state=STATE_CHUNK; } + else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) + _state=STATE_CHUNK_PARAMS; + else if (ch >= '0' && ch <= '9') + _chunkLength=_chunkLength * 16 + (ch - '0'); + else if (ch >= 'a' && ch <= 'f') + _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); + else if (ch >= 'A' && ch <= 'F') + _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); else - _state=STATE_CHUNK; - } - else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) - _state=STATE_CHUNK_PARAMS; - else if (ch >= '0' && ch <= '9') - _chunkLength=_chunkLength * 16 + (ch - '0'); - else if (ch >= 'a' && ch <= 'f') - _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); - else if (ch >= 'A' && ch <= 'F') - _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); - else - throw new IOException("bad chunk char: " + ch); - break; - } - - case STATE_CHUNK_PARAMS: - { - ch=_buffer.get(); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - { - _eol=ch; - if (_chunkLength == 0) - { - if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) - _eol=_buffer.get(); - _state=STATE_END; - _handler.messageComplete(_contentPosition); - return 1; - } - else - _state=STATE_CHUNK; - } - break; - } - - case STATE_CHUNK: - { - int remaining=_chunkLength - _chunkPosition; - if (remaining == 0) - { - _state=STATE_CHUNKED_CONTENT; + throw new IOException("bad chunk char: " + ch); + break; + } + + case STATE_CHUNK_PARAMS: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + if (_chunkLength == 0) + { + if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) + _eol=_buffer.get(); + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + returnBuffers(); + return 1; + } + else + _state=STATE_CHUNK; + } + break; + } + + case STATE_CHUNK: + { + int remaining=_chunkLength - _chunkPosition; + if (remaining == 0) + { + _state=STATE_CHUNKED_CONTENT; + break; + } + else if (length > remaining) + length=remaining; + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _chunkPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return 1; + } + + case STATE_SEEKING_EOF: + { + // Close if there is more data than CRLF + if (_buffer.length()>2) + { + _state=STATE_END; + _endp.close(); + } + else + { + // or if the data is not white space + while (_buffer.length()>0) + if (!Character.isWhitespace(_buffer.get())) + { + _state=STATE_END; + _endp.close(); + _buffer.clear(); + } + } + + _buffer.clear(); break; } - else if (length > remaining) - length=remaining; - chunk=_buffer.get(length); - _contentPosition += chunk.length(); - _chunkPosition += chunk.length(); - _contentView.update(chunk); - _handler.content(chunk); // May recurse here - // TODO adjust the _buffer to keep unconsumed content - return 1; } + + length=_buffer.length(); } - length=_buffer.length(); + return progress; + } + catch(HttpException e) + { + _persistent=false; + _state=STATE_SEEKING_EOF; + throw e; } - - return progress; } /* ------------------------------------------------------------------------------- */ /** fill the buffers from the endpoint - * + * */ - public long fill() throws IOException + protected int fill() throws IOException { // Do we have a buffer? if (_buffer==null) @@ -898,14 +1018,14 @@ public class HttpParser implements Parser _tok0=new View.CaseInsensitive(_buffer); _tok1=new View.CaseInsensitive(_buffer); } - + // Is there unconsumed content in body buffer if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent()) { _buffer=_body; return _buffer.length(); } - + // Shall we switch to a body buffer? if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null)) { @@ -913,23 +1033,27 @@ public class HttpParser implements Parser _body=_buffers.getBuffer(); _buffer=_body; } - + // Do we have somewhere to fill from? if (_endp != null ) { // Shall we compact the body? - if (_buffer==_body || _state>STATE_END) + if (_buffer==_body || _state>STATE_END) { _buffer.compact(); } - + // Are we full? - if (_buffer.space() == 0) - throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); - + if (_buffer.space() == 0) + { + LOG.warn("Full {}",_buffer.toDetailString()); + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); + } + try { - return _endp.fill(_buffer); + int filled = _endp.fill(_buffer); + return filled; } catch(IOException e) { @@ -941,45 +1065,12 @@ public class HttpParser implements Parser return -1; } - /* ------------------------------------------------------------------------------- */ - /** Skip any CRLFs in buffers - * - */ - public void skipCRLF() - { - - while (_header!=null && _header.length()>0) - { - byte ch = _header.peek(); - if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED) - { - _eol=ch; - _header.skip(1); - } - else - break; - } - - while (_body!=null && _body.length()>0) - { - byte ch = _body.peek(); - if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED) - { - _eol=ch; - _body.skip(1); - } - else - break; - } - - } - /* ------------------------------------------------------------------------------- */ public void reset() - { + { // reset state _contentView.setGetIndex(_contentView.putIndex()); - _state=STATE_START; + _state=_persistent?STATE_START:(_endp.isInputShutdown()?STATE_END:STATE_SEEKING_EOF); _contentLength=HttpTokens.UNKNOWN_CONTENT; _contentPosition=0; _length=0; @@ -1019,6 +1110,7 @@ public class HttpParser implements Parser _body.setMarkIndex(-1); _buffer=_header; + returnBuffers(); } @@ -1026,12 +1118,12 @@ public class HttpParser implements Parser public void returnBuffers() { if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null) - { + { if (_buffer==_body) _buffer=_header; if (_buffers!=null) _buffers.returnBuffer(_body); - _body=null; + _body=null; } if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null) @@ -1042,7 +1134,7 @@ public class HttpParser implements Parser _header=null; } } - + /* ------------------------------------------------------------------------------- */ public void setState(int state) { @@ -1055,13 +1147,17 @@ public class HttpParser implements Parser { return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode(); } - + /* ------------------------------------------------------------------------------- */ @Override public String toString() { - return "state=" + _state + " length=" + _length + " len=" + _contentLength; - } + return String.format("%s{s=%d,l=%d,c=%d}", + getClass().getSimpleName(), + _state, + _length, + _contentLength); + } /* ------------------------------------------------------------ */ public Buffer getHeaderBuffer() @@ -1072,7 +1168,7 @@ public class HttpParser implements Parser } return _header; } - + /* ------------------------------------------------------------ */ public Buffer getBodyBuffer() { @@ -1086,26 +1182,27 @@ public class HttpParser implements Parser public void setForceContentBuffer(boolean force) { _forceContentBuffer=force; - } - + } + /* ------------------------------------------------------------ */ public Buffer blockForContent(long maxIdleTime) throws IOException { if (_contentView.length()>0) return _contentView; - if (getState() <= HttpParser.STATE_END) + + if (getState() <= STATE_END || isState(STATE_SEEKING_EOF)) return null; - + try { parseNext(); - + // parse until some progress is made (or IOException thrown for timeout) - while(_contentView.length() == 0 && !isState(HttpParser.STATE_END) && _endp!=null && _endp.isOpen()) + while(_contentView.length() == 0 && !(isState(HttpParser.STATE_END)||isState(HttpParser.STATE_SEEKING_EOF)) && _endp!=null && _endp.isOpen()) { if (!_endp.isBlocking()) { - if (_endp.isBufferingInput() && parseNext()>0) + if (parseNext()>0) continue; if (!_endp.blockReadable(maxIdleTime)) @@ -1120,12 +1217,13 @@ public class HttpParser implements Parser } catch(IOException e) { + // TODO is this needed? _endp.close(); throw e; } - - return _contentView.length()>0?_contentView:null; - } + + return _contentView.length()>0?_contentView:null; + } /* ------------------------------------------------------------ */ /* (non-Javadoc) @@ -1143,11 +1241,11 @@ public class HttpParser implements Parser return 0; } - + parseNext(); return _contentView==null?0:_contentView.length(); } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -1175,15 +1273,18 @@ public class HttpParser implements Parser */ public abstract void startRequest(Buffer method, Buffer url, Buffer version) throws IOException; - + /** * This is the method called by parser when the HTTP request line is parsed */ public abstract void startResponse(Buffer version, int status, Buffer reason) throws IOException; + + public void earlyEOF() + {} } - + } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java index c953c67c175..5817c315b4f 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java @@ -26,13 +26,17 @@ public interface Parser boolean isComplete(); /** - * @return An indication of progress, typically the number of bytes filled plus the events parsed: -1 means EOF read, 0 no progress, >0 progress + * @return True if progress made * @throws IOException */ - int parseAvailable() throws IOException; + boolean parseAvailable() throws IOException; boolean isMoreInBuffer() throws IOException; boolean isIdle(); + + boolean isPersistent(); + + void setPersistent(boolean persistent); } 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 7e24daa3ebf..19da9e00104 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 @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; @@ -34,7 +34,7 @@ import org.eclipse.jetty.util.URIUtil; * /foo/bar - an exact path specification. * /foo/* - a prefix path specification (must end '/*'). * *.ext - a suffix path specification. - * / - the default path specification. + * / - the default path specification. * * Matching is performed in the following order *
  • Exact match. @@ -43,24 +43,24 @@ import org.eclipse.jetty.util.URIUtil; *
  • default. * * Multiple path specifications can be mapped by providing a list of - * specifications. By default this class uses characters ":," as path - * separators, unless configured differently by calling the static - * method @see PathMap#setPathSpecSeparators(String) + * specifications. By default this class uses characters ":," as path + * separators, unless configured differently by calling the static + * method @see PathMap#setPathSpecSeparators(String) *

    * Special characters within paths such as '?� and ';' are not treated specially - * as it is assumed they would have been either encoded in the original URL or + * as it is assumed they would have been either encoded in the original URL or * stripped from the path. *

    * This class is not synchronized. If concurrent modifications are * possible then it should be synchronized at a higher level. * - * + * */ public class PathMap extends HashMap implements Externalizable { /* ------------------------------------------------------------ */ private static String __pathSpecSeparators = ":,"; - + /* ------------------------------------------------------------ */ /** Set the path spec separator. * Multiple path specification may be included in a single string @@ -72,7 +72,7 @@ public class PathMap extends HashMap implements Externalizable { __pathSpecSeparators=s; } - + /* --------------------------------------------------------------- */ final StringMap _prefixMap=new StringMap(); final StringMap _suffixMap=new StringMap(); @@ -83,7 +83,7 @@ public class PathMap extends HashMap implements Externalizable Entry _default=null; final Set _entrySet; boolean _nodefault=false; - + /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ @@ -102,7 +102,7 @@ public class PathMap extends HashMap implements Externalizable _entrySet=entrySet(); _nodefault=nodefault; } - + /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ @@ -111,7 +111,7 @@ public class PathMap extends HashMap implements Externalizable super (capacity); _entrySet=entrySet(); } - + /* --------------------------------------------------------------- */ /** Construct from dictionary PathMap. */ @@ -120,7 +120,7 @@ public class PathMap extends HashMap implements Externalizable putAll(m); _entrySet=entrySet(); } - + /* ------------------------------------------------------------ */ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException @@ -128,7 +128,7 @@ public class PathMap extends HashMap implements Externalizable HashMap map = new HashMap(this); out.writeObject(map); } - + /* ------------------------------------------------------------ */ public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException @@ -136,7 +136,7 @@ public class PathMap extends HashMap implements Externalizable HashMap map = (HashMap)in.readObject(); this.putAll(map); } - + /* --------------------------------------------------------------- */ /** Add a single path match to the PathMap. * @param pathSpec The path specification, or comma separated list of @@ -148,16 +148,16 @@ public class PathMap extends HashMap implements Externalizable { StringTokenizer tok = new StringTokenizer(pathSpec.toString(),__pathSpecSeparators); Object old =null; - + while (tok.hasMoreTokens()) { String spec=tok.nextToken(); - + if (!spec.startsWith("/") && !spec.startsWith("*.")) throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); - + old = super.put(spec,object); - + // Make entry that was just created. Entry entry = new Entry(spec,object); @@ -176,7 +176,7 @@ public class PathMap extends HashMap implements Externalizable else if (spec.startsWith("*.")) _suffixMap.put(spec.substring(2),entry); else if (spec.equals(URIUtil.SLASH)) - { + { if (_nodefault) _exactMap.put(spec,entry); else @@ -193,7 +193,7 @@ public class PathMap extends HashMap implements Externalizable } } } - + return old; } @@ -209,8 +209,8 @@ public class PathMap extends HashMap implements Externalizable return entry.getValue(); return null; } - - + + /* --------------------------------------------------------------- */ /** Get the entry mapped by the best specification. * @param path the path. @@ -222,14 +222,14 @@ public class PathMap extends HashMap implements Externalizable if (path==null) return null; - - int l=path.length(); + + int l=path.length(); // try exact match entry=_exactMap.getEntry(path,0,l); if (entry!=null) return (Entry) entry.getValue(); - + // prefix search int i=l; while((i=path.lastIndexOf('/',i-1))>=0) @@ -238,11 +238,11 @@ public class PathMap extends HashMap implements Externalizable if (entry!=null) return (Entry) entry.getValue(); } - + // Prefix Default if (_prefixDefault!=null) return _prefixDefault; - + // Extension search i=0; while ((i=path.indexOf('.',i+1))>0) @@ -250,12 +250,12 @@ public class PathMap extends HashMap implements Externalizable entry=_suffixMap.getEntry(path,i+1,l-i-1); if (entry!=null) return (Entry) entry.getValue(); - } - + } + // Default return _default; } - + /* --------------------------------------------------------------- */ /** Get all entries matched by the path. * Best match first. @@ -263,20 +263,20 @@ public class PathMap extends HashMap implements Externalizable * @return LazyList of Map.Entry instances key=pathSpec */ public Object getLazyMatches(String path) - { + { Map.Entry entry; Object entries=null; if (path==null) return LazyList.getList(entries); - + int l=path.length(); // try exact match entry=_exactMap.getEntry(path,0,l); if (entry!=null) entries=LazyList.add(entries,entry.getValue()); - + // prefix search int i=l-1; while((i=path.lastIndexOf('/',i-1))>=0) @@ -285,11 +285,11 @@ public class PathMap extends HashMap implements Externalizable if (entry!=null) entries=LazyList.add(entries,entry.getValue()); } - + // Prefix Default if (_prefixDefault!=null) entries=LazyList.add(entries,_prefixDefault); - + // Extension search i=0; while ((i=path.indexOf('.',i+1))>0) @@ -305,13 +305,13 @@ public class PathMap extends HashMap implements Externalizable // Optimization for just the default if (entries==null) return _defaultSingletonList; - + entries=LazyList.add(entries,_default); } - + return entries; } - + /* --------------------------------------------------------------- */ /** Get all entries matched by the path. * Best match first. @@ -319,7 +319,7 @@ public class PathMap extends HashMap implements Externalizable * @return List of Map.Entry instances key=pathSpec */ public List getMatches(String path) - { + { return LazyList.getList(getLazyMatches(path)); } @@ -330,12 +330,12 @@ public class PathMap extends HashMap implements Externalizable * @return Whether the PathMap contains any entries that match this */ public boolean containsMatch(String path) - { + { Entry match = getMatch(path); return match!=null && !match.equals(_default); } - /* --------------------------------------------------------------- */ + /* --------------------------------------------------------------- */ @Override public Object remove(Object pathSpec) { @@ -362,7 +362,7 @@ public class PathMap extends HashMap implements Externalizable } return super.remove(pathSpec); } - + /* --------------------------------------------------------------- */ @Override public void clear() @@ -374,7 +374,7 @@ public class PathMap extends HashMap implements Externalizable _defaultSingletonList=null; super.clear(); } - + /* --------------------------------------------------------------- */ /** * @return true if match. @@ -397,7 +397,7 @@ public class PathMap extends HashMap implements Externalizable { if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) return true; - + if(isPathWildcardMatch(pathSpec, path)) return true; } @@ -419,24 +419,24 @@ public class PathMap extends HashMap implements Externalizable } return false; } - - + + /* --------------------------------------------------------------- */ /** Return the portion of a path that matches a path spec. * @return null if no match at all. */ public static String pathMatch(String pathSpec, String path) - { + { char c = pathSpec.charAt(0); - + if (c=='/') { if (pathSpec.length()==1) return path; - + if (pathSpec.equals(path)) return path; - + if (isPathWildcardMatch(pathSpec, path)) return path.substring(0,pathSpec.length()-2); } @@ -448,7 +448,7 @@ public class PathMap extends HashMap implements Externalizable } return null; } - + /* --------------------------------------------------------------- */ /** Return the portion of a path that is after a path spec. * @return The path info string @@ -456,12 +456,12 @@ public class PathMap extends HashMap implements Externalizable public static String pathInfo(String pathSpec, String path) { char c = pathSpec.charAt(0); - + if (c=='/') { if (pathSpec.length()==1) return null; - + boolean wildcard = isPathWildcardMatch(pathSpec, path); // handle the case where pathSpec uses a wildcard and path info is "/*" @@ -474,7 +474,7 @@ public class PathMap extends HashMap implements Externalizable return null; return path.substring(pathSpec.length()-2); } - } + } return null; } @@ -484,7 +484,7 @@ public class PathMap extends HashMap implements Externalizable * @param base The base the path is relative to. * @param pathSpec The spec of the path segment to ignore. * @param path the additional path - * @return base plus path with pathspec removed + * @return base plus path with pathspec removed */ public static String relativePath(String base, String pathSpec, @@ -508,7 +508,7 @@ public class PathMap extends HashMap implements Externalizable path = base + URIUtil.SLASH + info; return path; } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -516,7 +516,7 @@ public class PathMap extends HashMap implements Externalizable { private final Object key; private final Object value; - private String mapped; + private String mapped; private transient String string; Entry(Object key, Object value) @@ -529,7 +529,7 @@ public class PathMap extends HashMap implements Externalizable { return key; } - + public Object getValue() { return value; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/GzipResponseWrapper.java b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/GzipResponseWrapper.java index ce8f766d9f2..12275fe9104 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/GzipResponseWrapper.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/GzipResponseWrapper.java @@ -22,7 +22,6 @@ import java.io.UnsupportedEncodingException; import java.util.Set; import javax.servlet.ServletOutputStream; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; @@ -35,8 +34,8 @@ import org.eclipse.jetty.util.StringUtil; */ public class GzipResponseWrapper extends HttpServletResponseWrapper { - public static int DEFAULT_BUFFER_SIZE = 8192; - public static int DEFAULT_MIN_GZIP_SIZE = 256; + public static final int DEFAULT_BUFFER_SIZE = 8192; + public static final int DEFAULT_MIN_GZIP_SIZE = 256; private HttpServletRequest _request; private Set _mimeTypes; @@ -121,7 +120,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper public void setStatus(int sc, String sm) { super.setStatus(sc,sm); - if (sc<200||sc>=300) + if (sc<200 || sc==204 || sc==205 || sc>=300) noGzip(); } @@ -132,7 +131,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper public void setStatus(int sc) { super.setStatus(sc); - if (sc<200||sc>=300) + if (sc<200 || sc==204 || sc==205 ||sc>=300) noGzip(); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java index 52cecaaad1b..7a5d156f0e1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java @@ -1,1302 +1,24 @@ -//======================================================================== -//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. -//======================================================================== - package org.eclipse.jetty.http.ssl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidParameterException; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.Security; -import java.security.cert.CRL; -import java.security.cert.CertStore; -import java.security.cert.Certificate; -import java.security.cert.CollectionCertStoreParameters; -import java.security.cert.PKIXBuilderParameters; -import java.security.cert.X509CertSelector; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.net.ssl.CertPathTrustManagerParameters; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSessionContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509KeyManager; -import javax.net.ssl.X509TrustManager; - -import org.eclipse.jetty.http.security.Password; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.security.CertificateUtils; -import org.eclipse.jetty.util.security.CertificateValidator; - /* ------------------------------------------------------------ */ /** - * SslContextFactory is used to configure SSL connectors - * as well as HttpClient. It holds all SSL parameters and - * creates SSL context based on these parameters to be - * used by the SSL connectors. + * @deprecated Use org.eclipse.jetty.util.ssl.SslContextFactory */ -public class SslContextFactory extends AbstractLifeCycle +public class SslContextFactory extends org.eclipse.jetty.util.ssl.SslContextFactory { - public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM = - (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ? - "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm")); - public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM = - (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ? - "SunX509" : Security.getProperty("ssl.TrustManagerFactory.algorithm")); - - /** Default value for the keystore location path. */ - public static final String DEFAULT_KEYSTORE_PATH = - System.getProperty("user.home") + File.separator + ".keystore"; - - /** String name of key password property. */ - public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword"; - - /** String name of keystore password property. */ - public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password"; - - /** Excluded cipher suites. */ - private Set _excludeCipherSuites = null; - /** Included cipher suites. */ - private Set _includeCipherSuites = null; - - /** Keystore path. */ - private String _keyStorePath; - /** Keystore provider name */ - private String _keyStoreProvider; - /** Keystore type */ - private String _keyStoreType = "JKS"; - /** Keystore input stream */ - private InputStream _keyStoreInputStream; - - /** SSL certificate alias */ - private String _certAlias; - - /** Truststore path */ - private String _trustStorePath; - /** Truststore provider name */ - private String _trustStoreProvider; - /** Truststore type */ - private String _trustStoreType = "JKS"; - /** Truststore input stream */ - private InputStream _trustStoreInputStream; - - /** Set to true if client certificate authentication is required */ - private boolean _needClientAuth = false; - /** Set to true if client certificate authentication is desired */ - private boolean _wantClientAuth = false; - - /** Set to true if renegotiation is allowed */ - private boolean _allowRenegotiate = true; - - /** Keystore password */ - private transient Password _keyStorePassword; - /** Key manager password */ - private transient Password _keyManagerPassword; - /** Truststore password */ - private transient Password _trustStorePassword; - - /** SSL provider name */ - private String _sslProvider; - /** SSL protocol name */ - private String _sslProtocol = "TLS"; - - /** SecureRandom algorithm */ - private String _secureRandomAlgorithm; - /** KeyManager factory algorithm */ - private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM; - /** TrustManager factory algorithm */ - private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM; - - /** Set to true if SSL certificate validation is required */ - private boolean _validateCerts; - /** Set to true if SSL certificate of the peer validation is required */ - private boolean _validatePeerCerts; - /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */ - private int _maxCertPathLength = -1; - /** Path to file that contains Certificate Revocation List */ - private String _crlPath; - /** Set to true to enable CRL Distribution Points (CRLDP) support */ - private boolean _enableCRLDP = false; - /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */ - private boolean _enableOCSP = false; - /** Location of OCSP Responder */ - private String _ocspResponderURL; - - /** SSL keystore */ - private KeyStore _keyStore; - /** SSL truststore */ - private KeyStore _trustStore; - /** Set to true to enable SSL Session caching */ - private boolean _sessionCachingEnabled = true; - /** SSL session cache size */ - private int _sslSessionCacheSize; - /** SSL session timeout */ - private int _sslSessionTimeout; - - /** SSL context */ - private SSLContext _context; - - /* ------------------------------------------------------------ */ - /** - * Construct an instance of SslContextFactory - * Default constructor for use in XmlConfiguration files - */ public SslContextFactory() { + super(); + } + + public SslContextFactory(boolean trustAll) + { + super(trustAll); } - /* ------------------------------------------------------------ */ - /** - * Construct an instance of SslContextFactory - * @param keyStorePath default keystore location - */ public SslContextFactory(String keyStorePath) { - _keyStorePath = keyStorePath; - } - - /* ------------------------------------------------------------ */ - /** - * Create the SSLContext object and start the lifecycle - * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() - */ - @Override - protected void doStart() throws Exception - { - if (_context == null) - { - if (_keyStoreInputStream == null && _keyStorePath == null && - _trustStoreInputStream == null && _trustStorePath == null ) - { - // Create a trust manager that does not validate certificate chains - TrustManager trustAllCerts = new X509TrustManager() - { - public java.security.cert.X509Certificate[] getAcceptedIssuers() - { - return null; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) - { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) - { - } - }; - - _context = SSLContext.getInstance(_sslProtocol); - _context.init(null, new TrustManager[]{trustAllCerts}, null); - } - else - { - createSSLContext(); - } - } - } - - /* ------------------------------------------------------------ */ - /** - * @return The array of cipher suite names to exclude from - * {@link SSLEngine#setEnabledCipherSuites(String[])} - */ - public String[] getExcludeCipherSuites() - { - return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]); - } - - /* ------------------------------------------------------------ */ - /** - * @param cipherSuites - * The array of cipher suite names to exclude from - * {@link SSLEngine#setEnabledCipherSuites(String[])} - */ - public void setExcludeCipherSuites(String[] cipherSuites) - { - checkStarted(); - - _excludeCipherSuites = new HashSet(Arrays.asList(cipherSuites)); - } - - /* ------------------------------------------------------------ */ - /** - * @return The array of cipher suite names to include in - * {@link SSLEngine#setEnabledCipherSuites(String[])} - */ - public String[] getIncludeCipherSuites() - { - return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]); - } - - /* ------------------------------------------------------------ */ - /** - * @param cipherSuites - * The array of cipher suite names to include in - * {@link SSLEngine#setEnabledCipherSuites(String[])} - */ - public void setIncludeCipherSuites(String[] cipherSuites) - { - checkStarted(); - - _includeCipherSuites = new HashSet(Arrays.asList(cipherSuites)); - } - - /* ------------------------------------------------------------ */ - /** - * @return The file or URL of the SSL Key store. - */ - public String getKeyStore() - { - return _keyStorePath; - } - - /* ------------------------------------------------------------ */ - /** - * @param keyStorePath - * The file or URL of the SSL Key store. - */ - public void setKeyStore(String keyStorePath) - { - checkStarted(); - - _keyStorePath = keyStorePath; - } - - /* ------------------------------------------------------------ */ - /** - * @return The provider of the key store - */ - public String getKeyStoreProvider() - { - return _keyStoreProvider; - } - - /* ------------------------------------------------------------ */ - /** - * @param keyStoreProvider - * The provider of the key store - */ - public void setKeyStoreProvider(String keyStoreProvider) - { - checkStarted(); - - _keyStoreProvider = keyStoreProvider; - } - - /* ------------------------------------------------------------ */ - /** - * @return The type of the key store (default "JKS") - */ - public String getKeyStoreType() - { - return (_keyStoreType); - } - - /* ------------------------------------------------------------ */ - /** - * @param keyStoreType - * The type of the key store (default "JKS") - */ - public void setKeyStoreType(String keyStoreType) - { - checkStarted(); - - _keyStoreType = keyStoreType; - } - - /* ------------------------------------------------------------ */ - /** Get the _keyStoreInputStream. - * @return the _keyStoreInputStream - * - * @deprecated - */ - @Deprecated - public InputStream getKeyStoreInputStream() - { - checkConfig(); - - return _keyStoreInputStream; - } - - /* ------------------------------------------------------------ */ - /** Set the keyStoreInputStream. - * @param keyStoreInputStream the InputStream to the KeyStore - * - * @deprecated - */ - @Deprecated - public void setKeyStoreInputStream(InputStream keyStoreInputStream) - { - checkStarted(); - - _keyStoreInputStream = keyStoreInputStream; - } - - /* ------------------------------------------------------------ */ - /** - * @return Alias of SSL certificate for the connector - */ - public String getCertAlias() - { - return _certAlias; - } - - /* ------------------------------------------------------------ */ - /** - * @param certAlias - * Alias of SSL certificate for the connector - */ - public void setCertAlias(String certAlias) - { - checkStarted(); - - _certAlias = certAlias; - } - - /* ------------------------------------------------------------ */ - /** - * @return The file name or URL of the trust store location - */ - public String getTrustStore() - { - return _trustStorePath; - } - - /* ------------------------------------------------------------ */ - /** - * @param trustStorePath - * The file name or URL of the trust store location - */ - public void setTrustStore(String trustStorePath) - { - checkStarted(); - - _trustStorePath = trustStorePath; - } - - /* ------------------------------------------------------------ */ - /** - * @return The provider of the trust store - */ - public String getTrustStoreProvider() - { - return _trustStoreProvider; - } - - /* ------------------------------------------------------------ */ - /** - * @param trustStoreProvider - * The provider of the trust store - */ - public void setTrustStoreProvider(String trustStoreProvider) - { - checkStarted(); - - _trustStoreProvider = trustStoreProvider; - } - - /* ------------------------------------------------------------ */ - /** - * @return The type of the trust store (default "JKS") - */ - public String getTrustStoreType() - { - return _trustStoreType; - } - - /* ------------------------------------------------------------ */ - /** - * @param trustStoreType - * The type of the trust store (default "JKS") - */ - public void setTrustStoreType(String trustStoreType) - { - checkStarted(); - - _trustStoreType = trustStoreType; - } - - /* ------------------------------------------------------------ */ - /** Get the _trustStoreInputStream. - * @return the _trustStoreInputStream - * - * @deprecated - */ - @Deprecated - public InputStream getTrustStoreInputStream() - { - checkConfig(); - - return _trustStoreInputStream; - } - - /* ------------------------------------------------------------ */ - /** Set the _trustStoreInputStream. - * @param trustStoreInputStream the InputStream to the TrustStore - * - * @deprecated - */ - @Deprecated - public void setTrustStoreInputStream(InputStream trustStoreInputStream) - { - checkStarted(); - - _trustStoreInputStream = trustStoreInputStream; - } - - /* ------------------------------------------------------------ */ - /** - * @return True if SSL needs client authentication. - * @see SSLEngine#getNeedClientAuth() - */ - public boolean getNeedClientAuth() - { - return _needClientAuth; - } - - /* ------------------------------------------------------------ */ - /** - * @param needClientAuth - * True if SSL needs client authentication. - * @see SSLEngine#getNeedClientAuth() - */ - public void setNeedClientAuth(boolean needClientAuth) - { - checkStarted(); - - _needClientAuth = needClientAuth; - } - - /* ------------------------------------------------------------ */ - /** - * @return True if SSL wants client authentication. - * @see SSLEngine#getWantClientAuth() - */ - public boolean getWantClientAuth() - { - return _wantClientAuth; - } - - /* ------------------------------------------------------------ */ - /** - * @param wantClientAuth - * True if SSL wants client authentication. - * @see SSLEngine#getWantClientAuth() - */ - public void setWantClientAuth(boolean wantClientAuth) - { - checkStarted(); - - _wantClientAuth = wantClientAuth; - } - - /* ------------------------------------------------------------ */ - /** - * @return true if SSL certificate has to be validated - * @deprecated - */ - @Deprecated - public boolean getValidateCerts() - { - return _validateCerts; - } - - /* ------------------------------------------------------------ */ - /** - * @return true if SSL certificate has to be validated - */ - public boolean isValidateCerts() - { - return _validateCerts; - } - - /* ------------------------------------------------------------ */ - /** - * @param validateCerts - * true if SSL certificates have to be validated - */ - public void setValidateCerts(boolean validateCerts) - { - checkStarted(); - - _validateCerts = validateCerts; - } - - /* ------------------------------------------------------------ */ - /** - * @return true if SSL certificates of the peer have to be validated - */ - public boolean isValidatePeerCerts() - { - return _validatePeerCerts; - } - - /* ------------------------------------------------------------ */ - /** - * @param validatePeerCerts - * true if SSL certificates of the peer have to be validated - */ - public void setValidatePeerCerts(boolean validatePeerCerts) - { - checkStarted(); - - _validatePeerCerts = validatePeerCerts; - } - - /* ------------------------------------------------------------ */ - /** - * @return True if SSL re-negotiation is allowed (default false) - */ - public boolean isAllowRenegotiate() - { - return _allowRenegotiate; - } - - /* ------------------------------------------------------------ */ - /** - * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered - * a vulnerability in SSL/TLS with re-negotiation. If your JVM - * does not have CVE-2009-3555 fixed, then re-negotiation should - * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban - * of renegotiates in u19 and with RFC5746 in u22. - * - * @param allowRenegotiate - * true if re-negotiation is allowed (default false) - */ - public void setAllowRenegotiate(boolean allowRenegotiate) - { - checkStarted(); - - _allowRenegotiate = allowRenegotiate; - } - - /* ------------------------------------------------------------ */ - /** - * @param password - * The password for the key store - */ - public void setKeyStorePassword(String password) - { - checkStarted(); - - _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); - } - - /* ------------------------------------------------------------ */ - /** - * @param password - * The password (if any) for the specific key within the key store - */ - public void setKeyManagerPassword(String password) - { - checkStarted(); - - _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null); - } - - /* ------------------------------------------------------------ */ - /** - * @param password - * The password for the trust store - */ - public void setTrustStorePassword(String password) - { - checkStarted(); - - _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); - } - - /* ------------------------------------------------------------ */ - /** - * @return The SSL provider name, which if set is passed to - * {@link SSLContext#getInstance(String, String)} - */ - public String getProvider() - { - return _sslProvider; - } - - /* ------------------------------------------------------------ */ - /** - * @param provider - * The SSL provider name, which if set is passed to - * {@link SSLContext#getInstance(String, String)} - */ - public void setProvider(String provider) - { - checkStarted(); - - _sslProvider = provider; - } - - /* ------------------------------------------------------------ */ - /** - * @return The SSL protocol (default "TLS") passed to - * {@link SSLContext#getInstance(String, String)} - */ - public String getProtocol() - { - return _sslProtocol; - } - - /* ------------------------------------------------------------ */ - /** - * @param protocol - * The SSL protocol (default "TLS") passed to - * {@link SSLContext#getInstance(String, String)} - */ - public void setProtocol(String protocol) - { - checkStarted(); - - _sslProtocol = protocol; - } - - /* ------------------------------------------------------------ */ - /** - * @return The algorithm name, which if set is passed to - * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to - * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} - */ - public String getSecureRandomAlgorithm() - { - return _secureRandomAlgorithm; - } - - /* ------------------------------------------------------------ */ - /** - * @param algorithm - * The algorithm name, which if set is passed to - * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to - * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} - */ - public void setSecureRandomAlgorithm(String algorithm) - { - checkStarted(); - - _secureRandomAlgorithm = algorithm; - } - - /* ------------------------------------------------------------ */ - /** - * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} - */ - public String getSslKeyManagerFactoryAlgorithm() - { - return (_keyManagerFactoryAlgorithm); - } - - /* ------------------------------------------------------------ */ - /** - * @param algorithm - * The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} - */ - public void setSslKeyManagerFactoryAlgorithm(String algorithm) - { - checkStarted(); - - _keyManagerFactoryAlgorithm = algorithm; - } - - /* ------------------------------------------------------------ */ - /** - * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} - */ - public String getTrustManagerFactoryAlgorithm() - { - return (_trustManagerFactoryAlgorithm); - } - - /* ------------------------------------------------------------ */ - /** - * @param algorithm - * The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} - */ - public void setTrustManagerFactoryAlgorithm(String algorithm) - { - checkStarted(); - - _trustManagerFactoryAlgorithm = algorithm; - } - - /* ------------------------------------------------------------ */ - /** - * @return Path to file that contains Certificate Revocation List - */ - public String getCrlPath() - { - return _crlPath; - } - - /* ------------------------------------------------------------ */ - /** - * @param crlPath - * Path to file that contains Certificate Revocation List - */ - public void setCrlPath(String crlPath) - { - checkStarted(); - - _crlPath = crlPath; - } - - /* ------------------------------------------------------------ */ - /** - * @return Maximum number of intermediate certificates in - * the certification path (-1 for unlimited) - */ - public int getMaxCertPathLength() - { - return _maxCertPathLength; - } - - /* ------------------------------------------------------------ */ - /** - * @param maxCertPathLength - * maximum number of intermediate certificates in - * the certification path (-1 for unlimited) - */ - public void setMaxCertPathLength(int maxCertPathLength) - { - checkStarted(); - - _maxCertPathLength = maxCertPathLength; - } - - /* ------------------------------------------------------------ */ - /** - * @return The SSLContext - */ - public SSLContext getSslContext() - { - return _context; - } - - /* ------------------------------------------------------------ */ - /** - * @param sslContext - * Set a preconfigured SSLContext - */ - public void setSslContext(SSLContext sslContext) - { - checkStarted(); - - _context = sslContext; - } - - /* ------------------------------------------------------------ */ - /** - * @throws Exception - */ - protected void createSSLContext() throws Exception - { - // verify that keystore and truststore - // parameters are set up correctly - checkConfig(); - - KeyStore keyStore = loadKeyStore(); - KeyStore trustStore = loadTrustStore(); - - Collection crls = loadCRL(_crlPath); - - if (_validateCerts && keyStore != null) - { - if (_certAlias == null) - { - List aliases = Collections.list(keyStore.aliases()); - _certAlias = aliases.size() == 1 ? aliases.get(0) : null; - } - - Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias); - if (cert == null) - { - throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias)); - } - - CertificateValidator validator = new CertificateValidator(trustStore, crls); - validator.setMaxCertPathLength(_maxCertPathLength); - validator.setEnableCRLDP(_enableCRLDP); - validator.setEnableOCSP(_enableOCSP); - validator.setOcspResponderURL(_ocspResponderURL); - validator.validate(keyStore, cert); - } - - KeyManager[] keyManagers = getKeyManagers(keyStore); - TrustManager[] trustManagers = getTrustManagers(trustStore,crls); - - SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); - _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider); - _context.init(keyManagers,trustManagers,secureRandom); - - SSLSessionContext sslSessionContext = _context.getServerSessionContext(); - sslSessionContext.setSessionCacheSize(_sslSessionCacheSize); - sslSessionContext.setSessionTimeout(_sslSessionTimeout); - } - - /* ------------------------------------------------------------ */ - /** - * Override this method to provide alternate way to load a keystore. - * - * @return the key store instance - * @throws Exception - */ - protected KeyStore loadKeyStore() throws Exception - { - return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream, - _keyStorePath, _keyStoreType, _keyStoreProvider, - _keyStorePassword==null? null: _keyStorePassword.toString()); - } - - /* ------------------------------------------------------------ */ - /** - * Override this method to provide alternate way to load a truststore. - * - * @return the key store instance - * @throws Exception - */ - protected KeyStore loadTrustStore() throws Exception - { - return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream, - _trustStorePath, _trustStoreType, _trustStoreProvider, - _trustStorePassword==null? null: _trustStorePassword.toString()); - } - - /* ------------------------------------------------------------ */ - /** - * Loads keystore using an input stream or a file path in the same - * order of precedence. - * - * Required for integrations to be able to override the mechanism - * used to load a keystore in order to provide their own implementation. - * - * @param storeStream keystore input stream - * @param storePath path of keystore file - * @param storeType keystore type - * @param storeProvider keystore provider - * @param storePassword keystore password - * @return created keystore - * @throws Exception - * - * @deprecated - */ - @Deprecated - protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception - { - return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword); - } - - /* ------------------------------------------------------------ */ - /** - * Loads certificate revocation list (CRL) from a file. - * - * Required for integrations to be able to override the mechanism used to - * load CRL in order to provide their own implementation. - * - * @param crlPath path of certificate revocation list file - * @return Collection of CRL's - * @throws Exception - */ - protected Collection loadCRL(String crlPath) throws Exception - { - return CertificateUtils.loadCRL(crlPath); - } - - /* ------------------------------------------------------------ */ - protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception - { - KeyManager[] managers = null; - - if (keyStore != null) - { - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm); - keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray()); - managers = keyManagerFactory.getKeyManagers(); - - if (_certAlias != null) - { - for (int idx = 0; idx < managers.length; idx++) - { - if (managers[idx] instanceof X509KeyManager) - { - managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]); - } - } - } - } - - return managers; - } - - /* ------------------------------------------------------------ */ - protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection crls) throws Exception - { - TrustManager[] managers = null; - if (trustStore != null) - { - // Revocation checking is only supported for PKIX algorithm - if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX")) - { - PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector()); - - // Set maximum certification path length - pbParams.setMaxPathLength(_maxCertPathLength); - - // Make sure revocation checking is enabled - pbParams.setRevocationEnabled(true); - - if (crls != null && !crls.isEmpty()) - { - pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls))); - } - - if (_enableCRLDP) - { - // Enable Certificate Revocation List Distribution Points (CRLDP) support - System.setProperty("com.sun.security.enableCRLDP","true"); - } - - if (_enableOCSP) - { - // Enable On-Line Certificate Status Protocol (OCSP) support - Security.setProperty("ocsp.enable","true"); - - if (_ocspResponderURL != null) - { - // Override location of OCSP Responder - Security.setProperty("ocsp.responderURL", _ocspResponderURL); - } - } - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); - trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams)); - - managers = trustManagerFactory.getTrustManagers(); - } - else - { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); - trustManagerFactory.init(trustStore); - - managers = trustManagerFactory.getTrustManagers(); - } - } - - return managers; - } - - /* ------------------------------------------------------------ */ - /** - * Check configuration. Ensures that if keystore has been - * configured but there's no truststore, that keystore is - * used as truststore. - * @return true SslContextFactory configuration can be used in server connector. - */ - public boolean checkConfig() - { - boolean check = true; - if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null) - { - // configuration doesn't have a valid keystore - check = false; - } - else - { - // if the keystore has been configured but there is no - // truststore configured, use the keystore as the truststore - if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null) - { - _trustStore = _keyStore; - _trustStorePath = _keyStorePath; - _trustStoreInputStream = _keyStoreInputStream; - _trustStoreType = _keyStoreType; - _trustStoreProvider = _keyStoreProvider; - _trustStorePassword = _keyStorePassword; - _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm; - } - } - - // It's the same stream we cannot read it twice, so read it once in memory - if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream) - { - try - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - IO.copy(_keyStoreInputStream, baos); - _keyStoreInputStream.close(); - - _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); - _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); - } - catch (Exception ex) - { - throw new RuntimeException(ex); - } - } - - return check; - } - - /* ------------------------------------------------------------ */ - /** - * Select cipher suites to be used by the connector - * based on configured inclusion and exclusion lists - * as well as enabled and supported cipher suite lists. - * @param enabledCipherSuites Array of enabled cipher suites - * @param supportedCipherSuites Array of supported cipher suites - * @return Array of cipher suites to enable - */ - public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) - { - Set selectedCipherSuites = null; - if (enabledCipherSuites != null) - { - selectedCipherSuites = new HashSet(Arrays.asList(enabledCipherSuites)); - } - else - { - selectedCipherSuites = new HashSet(); - } - - if ((supportedCipherSuites != null && supportedCipherSuites.length > 0) && - (_includeCipherSuites != null && _includeCipherSuites.size() > 0)) - { - Set supportedCSList = new HashSet(Arrays.asList(supportedCipherSuites)); - - for (String cipherName : _includeCipherSuites) - { - if ((!selectedCipherSuites.contains(cipherName)) && - supportedCSList.contains(cipherName)) - { - selectedCipherSuites.add(cipherName); - } - } - } - - if (_excludeCipherSuites != null && _excludeCipherSuites.size() > 0) - { - for (String cipherName : _excludeCipherSuites) - { - if (selectedCipherSuites.contains(cipherName)) - { - selectedCipherSuites.remove(cipherName); - } - } - } - - return selectedCipherSuites.toArray(new String[selectedCipherSuites.size()]); - } - - /* ------------------------------------------------------------ */ - /** - * Check if the lifecycle has been started and throw runtime exception - */ - protected void checkStarted() - { - if (isStarted()) - { - throw new IllegalStateException("Cannot modify configuration after SslContextFactory was started"); - } - } - - /* ------------------------------------------------------------ */ - /** - * @return true if CRL Distribution Points support is enabled - */ - public boolean isEnableCRLDP() - { - return _enableCRLDP; - } - - /* ------------------------------------------------------------ */ - /** Enables CRL Distribution Points Support - * @param enableCRLDP true - turn on, false - turns off - */ - public void setEnableCRLDP(boolean enableCRLDP) - { - checkStarted(); - - _enableCRLDP = enableCRLDP; - } - - /* ------------------------------------------------------------ */ - /** - * @return true if On-Line Certificate Status Protocol support is enabled - */ - public boolean isEnableOCSP() - { - return _enableOCSP; - } - - /* ------------------------------------------------------------ */ - /** Enables On-Line Certificate Status Protocol support - * @param enableOCSP true - turn on, false - turn off - */ - public void setEnableOCSP(boolean enableOCSP) - { - checkStarted(); - - _enableOCSP = enableOCSP; - } - - /* ------------------------------------------------------------ */ - /** - * @return Location of the OCSP Responder - */ - public String getOcspResponderURL() - { - return _ocspResponderURL; - } - - /* ------------------------------------------------------------ */ - /** Set the location of the OCSP Responder. - * @param ocspResponderURL location of the OCSP Responder - */ - public void setOcspResponderURL(String ocspResponderURL) - { - checkStarted(); - - _ocspResponderURL = ocspResponderURL; - } - - /* ------------------------------------------------------------ */ - /** Set the key store. - * @param keyStore the key store to set - */ - public void setKeyStore(KeyStore keyStore) - { - checkStarted(); - - _keyStore = keyStore; - } - - /* ------------------------------------------------------------ */ - /** Set the trust store. - * @param trustStore the trust store to set - */ - public void setTrustStore(KeyStore trustStore) - { - checkStarted(); - - _trustStore = trustStore; - } - - /* ------------------------------------------------------------ */ - /** Set the key store resource. - * @param resource the key store resource to set - */ - public void setKeyStoreResource(Resource resource) - { - checkStarted(); - - try - { - _keyStoreInputStream = resource.getInputStream(); - } - catch (IOException e) - { - throw new InvalidParameterException("Unable to get resource "+ - "input stream for resource "+resource.toString()); - } - } - - /* ------------------------------------------------------------ */ - /** Set the trust store resource. - * @param resource the trust store resource to set - */ - public void setTrustStore(Resource resource) - { - checkStarted(); - - try - { - _trustStoreInputStream = resource.getInputStream(); - } - catch (IOException e) - { - throw new InvalidParameterException("Unable to get resource "+ - "input stream for resource "+resource.toString()); - } - } - - /* ------------------------------------------------------------ */ - /** - * @return true if SSL Session caching is enabled - */ - public boolean isSessionCachingEnabled() - { - return _sessionCachingEnabled; - } - - /* ------------------------------------------------------------ */ - /** Set the flag to enable SSL Session caching. - * @param enableSessionCaching the value of the flag - */ - public void setSessionCachingEnabled(boolean enableSessionCaching) - { - _sessionCachingEnabled = enableSessionCaching; - } - - /* ------------------------------------------------------------ */ - /** Get SSL session cache size. - * @return SSL session cache size - */ - public int getSslSessionCacheSize() - { - return _sslSessionCacheSize; - } - - /* ------------------------------------------------------------ */ - /** SEt SSL session cache size. - * @param sslSessionCacheSize SSL session cache size to set - */ - public void setSslSessionCacheSize(int sslSessionCacheSize) - { - _sslSessionCacheSize = sslSessionCacheSize; - } - - /* ------------------------------------------------------------ */ - /** Get SSL session timeout. - * @return SSL session timeout - */ - public int getSslSessionTimeout() - { - return _sslSessionTimeout; - } - - /* ------------------------------------------------------------ */ - /** Set SSL session timeout. - * @param sslSessionTimeout SSL session timeout to set - */ - public void setSslSessionTimeout(int sslSessionTimeout) - { - _sslSessionTimeout = sslSessionTimeout; + super(keyStorePath); } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 6120f942aec..3ae9a409fd0 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -14,10 +14,9 @@ package org.eclipse.jetty.http; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; import java.util.Enumeration; import java.util.HashSet; diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java index 46841564daa..18012e950f5 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java @@ -138,7 +138,7 @@ public class HttpGeneratorClientTest String t="v="+v+",r="+r+",chunks="+chunks+",c="+c+",tr="+tr[r]; // System.err.println(t); - hb.reset(true); + hb.reset(); endp.reset(); fields.clear(); @@ -193,7 +193,7 @@ public class HttpGeneratorClientTest } private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; - private class TR + private static class TR { private String[] values=new String[headers.length]; private String body; diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java index 547d4647bb9..bceb1541e9d 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java @@ -86,7 +86,7 @@ public class HttpGeneratorTest String t="v="+v+",r="+r+",chunks="+chunks+",connect="+c+",tr="+tr[r]; // System.err.println(t); - hb.reset(true); + hb.reset(); endp.reset(); fields.clear(); @@ -138,7 +138,7 @@ public class HttpGeneratorTest } private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; - private class TR + private static class TR { private int _code; private String _body; diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index ac4ae8a1552..e861721de0b 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -210,6 +210,7 @@ public class HttpParserTest StringEndPoint io=new StringEndPoint(); io.setInput( "GET /mp HTTP/1.0\015\012" + + "Connection: Keep-Alive\015\012" + "Header1: value1\015\012" + "Transfer-Encoding: chunked\015\012" + "\015\012" @@ -219,10 +220,12 @@ public class HttpParserTest + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + "0\015\012" + "POST /foo HTTP/1.0\015\012" + + "Connection: Keep-Alive\015\012" + "Header2: value2\015\012" + "Content-Length: 0\015\012" + "\015\012" + "PUT /doodle HTTP/1.0\015\012" + + "Connection: close\015\012" + "Header3: value3\015\012" + "Content-Length: 10\015\012" + "\015\012" @@ -238,27 +241,27 @@ public class HttpParserTest assertEquals("GET", f0); assertEquals("/mp", f1); assertEquals("HTTP/1.0", f2); - assertEquals(1, h); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); + assertEquals(2, h); + assertEquals("Header1", hdr[1]); + assertEquals("value1", val[1]); assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content); parser.parse(); assertEquals("POST", f0); assertEquals("/foo", f1); assertEquals("HTTP/1.0", f2); - assertEquals(1, h); - assertEquals("Header2", hdr[0]); - assertEquals("value2", val[0]); + assertEquals(2, h); + assertEquals("Header2", hdr[1]); + assertEquals("value2", val[1]); assertEquals(null, _content); parser.parse(); assertEquals("PUT", f0); assertEquals("/doodle", f1); assertEquals("HTTP/1.0", f2); - assertEquals(1, h); - assertEquals("Header3", hdr[0]); - assertEquals("value3", val[0]); + assertEquals(2, h); + assertEquals("Header3", hdr[1]); + assertEquals("value3", val[1]); assertEquals("0123456789", _content); } @@ -266,7 +269,8 @@ public class HttpParserTest public void testStreamParse() throws Exception { StringEndPoint io=new StringEndPoint(); - String http="GET / HTTP/1.0\015\012" + String http="GET / HTTP/1.1\015\012" + + "Host: test\015\012" + "Header1: value1\015\012" + "Transfer-Encoding: chunked\015\012" + "\015\012" @@ -275,11 +279,14 @@ public class HttpParserTest + "1a\015\012" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + "0\015\012" - + "POST /foo HTTP/1.0\015\012" + + "POST /foo HTTP/1.1\015\012" + + "Host: test\015\012" + "Header2: value2\015\012" + "Content-Length: 0\015\012" + "\015\012" - + "PUT /doodle HTTP/1.0\015\012" + + "PUT /doodle HTTP/1.1\015\012" + + "Host: test\015\012" + + "Connection: close\015\012" + "Header3: value3\015\012" + "Content-Length: 10\015\012" + "\015\012" @@ -296,15 +303,17 @@ public class HttpParserTest http.length() - 2, http.length() / 2, http.length() / 3, - 64, + 128, 32 }; for (int t= 0; t < tests.length; t++) { - String tst="t"+tests[t]; + String tst="t"+t+"="+tests[t]; try - { + { + f0=f1=f2=null; + h=0; ByteArrayBuffer buffer= new ByteArrayBuffer(tests[t]); ByteArrayBuffer content=new ByteArrayBuffer(8192); SimpleBuffers buffers=new SimpleBuffers(buffer,content); @@ -314,31 +323,32 @@ public class HttpParserTest io.setInput(http); + // System.err.println(tst); parser.parse(); assertEquals(tst,"GET", f0); assertEquals(tst,"/", f1); - assertEquals(tst,"HTTP/1.0", f2); - assertEquals(tst,1, h); - assertEquals(tst,"Header1", hdr[0]); - assertEquals(tst,"value1", val[0]); + assertEquals(tst,"HTTP/1.1", f2); + assertEquals(tst,2, h); + assertEquals(tst,"Header1", hdr[1]); + assertEquals(tst,"value1", val[1]); assertEquals(tst,"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content); parser.parse(); assertEquals(tst,"POST", f0); assertEquals(tst,"/foo", f1); - assertEquals(tst,"HTTP/1.0", f2); - assertEquals(tst,1, h); - assertEquals(tst,"Header2", hdr[0]); - assertEquals(tst,"value2", val[0]); + assertEquals(tst,"HTTP/1.1", f2); + assertEquals(tst,2, h); + assertEquals(tst,"Header2", hdr[1]); + assertEquals(tst,"value2", val[1]); assertEquals(tst,null, _content); parser.parse(); assertEquals(tst,"PUT", f0); assertEquals(tst,"/doodle", f1); - assertEquals(tst,"HTTP/1.0", f2); - assertEquals(tst,1, h); - assertEquals(tst,"Header3", hdr[0]); - assertEquals(tst,"value3", val[0]); + assertEquals(tst,"HTTP/1.1", f2); + assertEquals(tst,3, h); + assertEquals(tst,"Header3", hdr[2]); + assertEquals(tst,"value3", val[2]); assertEquals(tst,"0123456789", _content); } catch(Exception e) @@ -401,7 +411,7 @@ public class HttpParserTest StringEndPoint io=new StringEndPoint(); io.setInput( "HTTP/1.1 204 No-Content\015\012" - + "Connection: close\015\012" + + "Header: value\015\012" + "\015\012" + "HTTP/1.1 200 Correct\015\012" + "Content-Length: 10\015\012" @@ -500,6 +510,36 @@ public class HttpParserTest assertTrue(messageCompleted); } + @Test + public void testSeekEOF() throws Exception + { + StringEndPoint io=new StringEndPoint(); + io.setInput( + "HTTP/1.1 200 OK\015\012" + + "Content-Length: 0\015\012" + + "Connection: close\015\012" + + "\015\012" + + "\015\012" // extra CRLF ignored + + "HTTP/1.1 400 OK\015\012"); // extra data causes close + + + ByteArrayBuffer buffer= new ByteArrayBuffer(4096); + SimpleBuffers buffers=new SimpleBuffers(buffer,null); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser(buffers,io, handler); + + parser.parse(); + assertEquals("HTTP/1.1", f0); + assertEquals("200", f1); + assertEquals("OK", f2); + assertEquals(null,_content); + assertTrue(headerCompleted); + assertTrue(messageCompleted); + + + } + private String _content; private String f0; private String f1; diff --git a/jetty-http/src/test/resources/keystore b/jetty-http/src/test/resources/keystore new file mode 100644 index 00000000000..b727bd0fb77 Binary files /dev/null and b/jetty-http/src/test/resources/keystore differ diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml index 4e1a9384ba4..8a5f69852a6 100644 --- a/jetty-io/pom.xml +++ b/jetty-io/pom.xml @@ -2,7 +2,7 @@ jetty-project org.eclipse.jetty - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 org.eclipse.jetty diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index 04a6e26ec87..ef80cccb2ff 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -15,13 +15,13 @@ public abstract class AbstractConnection implements Connection public AbstractConnection(EndPoint endp) { - _endp=endp; + _endp=(EndPoint)endp; _timeStamp = System.currentTimeMillis(); } - + public AbstractConnection(EndPoint endp,long timestamp) { - _endp=endp; + _endp=(EndPoint)endp; _timeStamp = timestamp; } @@ -29,17 +29,21 @@ public abstract class AbstractConnection implements Connection { return _timeStamp; } - + public EndPoint getEndPoint() { return _endp; } - public void idleExpired() + public void onIdleExpired(long idleForMs) { try { - _endp.shutdownOutput(); + LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp); + if (_endp.isInputShutdown() || _endp.isOutputShutdown()) + _endp.close(); + else + _endp.shutdownOutput(); } catch(IOException e) { @@ -52,13 +56,12 @@ public abstract class AbstractConnection implements Connection catch(IOException e2) { LOG.ignore(e2); - } } } - + public String toString() { - return super.toString()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); + return String.format("%s@%x", getClass().getSimpleName(), hashCode()); } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java index 5eebc36ad80..992ad7a195a 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java @@ -13,36 +13,59 @@ package org.eclipse.jetty.io; -public interface AsyncEndPoint extends EndPoint +import org.eclipse.jetty.util.thread.Timeout; + +public interface AsyncEndPoint extends ConnectedEndPoint { /* ------------------------------------------------------------ */ /** * Dispatch the endpoint to a thread to attend to it. * */ - public void dispatch(); - - /** - * @return true if this endpoint can accept a dispatch. False if the - * endpoint cannot accept a dispatched (eg is suspended or already dispatched) - */ - public boolean isReadyForDispatch(); + public void asyncDispatch(); /* ------------------------------------------------------------ */ /** Schedule a write dispatch. * Set the endpoint to not be writable and schedule a dispatch when * it becomes writable. */ - public void scheduleWrite(); - - /* ------------------------------------------------------------ */ - /** Schedule a call to the idle timeout - */ - public void scheduleIdle(); - - /* ------------------------------------------------------------ */ - /** Cancel a call to the idle timeout - */ - public void cancelIdle(); + public void scheduleWrite(); + /* ------------------------------------------------------------ */ + /** Callback when idle. + *

    An endpoint is idle if there has been no IO activity for + * {@link #getMaxIdleTime()} and {@link #isCheckForIdle()} is true. + * @param idleForMs TODO + */ + public void onIdleExpired(long idleForMs); + + /* ------------------------------------------------------------ */ + /** Set if the endpoint should be checked for idleness + */ + public void setCheckForIdle(boolean check); + + /* ------------------------------------------------------------ */ + /** Get if the endpoint should be checked for idleness + */ + public boolean isCheckForIdle(); + + + /* ------------------------------------------------------------ */ + public boolean isWritable(); + + /* ------------------------------------------------------------ */ + /** + * @return True if IO has been successfully performed since the last call to {@link #hasProgressed()} + */ + public boolean hasProgressed(); + + /* ------------------------------------------------------------ */ + /** + */ + public void scheduleTimeout(Timeout.Task task, long timeoutMs); + + /* ------------------------------------------------------------ */ + /** + */ + public void cancelTimeout(Timeout.Task task); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java index 904961087dc..0abc1270182 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java @@ -103,6 +103,12 @@ public class BufferCache return lookup(buffer).toString(); } + public int getOrdinal(String value) + { + CachedBuffer buffer = (CachedBuffer)_stringMap.get(value); + return buffer==null?-1:buffer.getOrdinal(); + } + public int getOrdinal(Buffer buffer) { if (buffer instanceof CachedBuffer) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java index c1aebb88829..275d2f3b77d 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java @@ -46,7 +46,6 @@ public class BufferDateCache extends DateCache public synchronized Buffer formatBuffer(long date) { String d = super.format(date); - //noinspection StringEquality if (d==_last) return _buffer; _last=d; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java index fcae2f99fda..a0e4f8e96df 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io; @@ -19,7 +19,7 @@ import java.io.IOException; /* ------------------------------------------------------------ */ /** ByteArrayEndPoint. - * + * * */ public class ByteArrayEndPoint implements ConnectedEndPoint @@ -36,12 +36,12 @@ public class ByteArrayEndPoint implements ConnectedEndPoint /* ------------------------------------------------------------ */ /** - * + * */ public ByteArrayEndPoint() { } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.ConnectedEndPoint#getConnection() @@ -80,7 +80,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint /* ------------------------------------------------------------ */ /** - * + * */ public ByteArrayEndPoint(byte[] input, int outputSize) { @@ -121,9 +121,9 @@ public class ByteArrayEndPoint implements ConnectedEndPoint { _out = out; } - + /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#isOpen() */ public boolean isOpen() @@ -150,7 +150,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#isBlocking() */ public boolean isBlocking() @@ -171,7 +171,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#shutdownOutput() */ public void shutdownOutput() throws IOException @@ -180,16 +180,16 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#shutdownInput() */ public void shutdownInput() throws IOException { close(); } - + /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#close() */ public void close() throws IOException @@ -198,30 +198,30 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer) */ public int fill(Buffer buffer) throws IOException { if (_closed) throw new IOException("CLOSED"); - + if (_in!=null && _in.length()>0) { int len = buffer.put(_in); _in.skip(len); return len; } - + if (_in!=null && _in.length()==0 && _nonBlocking) return 0; - + close(); return -1; } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer) */ public int flush(Buffer buffer) throws IOException @@ -252,24 +252,24 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) */ public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException { if (_closed) throw new IOException("CLOSED"); - + int flushed=0; - + if (header!=null && header.length()>0) flushed=flush(header); - + if (header==null || header.length()==0) { if (buffer!=null && buffer.length()>0) flushed+=flush(buffer); - + if (buffer==null || buffer.length()==0) { if (trailer!=null && trailer.length()>0) @@ -278,13 +278,13 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } } } - + return flushed; } /* ------------------------------------------------------------ */ /** - * + * */ public void reset() { @@ -296,7 +296,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalAddr() */ public String getLocalAddr() @@ -305,7 +305,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalHost() */ public String getLocalHost() @@ -314,7 +314,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalPort() */ public int getLocalPort() @@ -323,7 +323,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteAddr() */ public String getRemoteAddr() @@ -332,7 +332,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteHost() */ public String getRemoteHost() @@ -341,7 +341,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemotePort() */ public int getRemotePort() @@ -350,7 +350,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getConnection() */ public Object getTransport() @@ -360,27 +360,9 @@ public class ByteArrayEndPoint implements ConnectedEndPoint /* ------------------------------------------------------------ */ public void flush() throws IOException - { - } - - /* ------------------------------------------------------------ */ - public boolean isBufferingInput() { - return false; } - - /* ------------------------------------------------------------ */ - public boolean isBufferingOutput() - { - return false; - } - - /* ------------------------------------------------------------ */ - public boolean isBufferred() - { - return false; - } - + /* ------------------------------------------------------------ */ /** * @return the growOutput diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java index 431964afe8f..19cf3bee60e 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java @@ -50,10 +50,11 @@ public interface Connection /** * Called when the connection is closed */ - void closed(); + void onClose(); /** * Called when the connection idle timeout expires + * @param idleForMs TODO */ - void idleExpired(); + void onIdleExpired(long idleForMs); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 21919c5fe12..231b752b9ee 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io; @@ -17,7 +17,7 @@ import java.io.IOException; /** - * + * * A transport EndPoint */ public interface EndPoint @@ -28,37 +28,39 @@ public interface EndPoint void shutdownOutput() throws IOException; boolean isOutputShutdown(); - + /** * Shutdown any backing input stream associated with the endpoint */ void shutdownInput() throws IOException; - + boolean isInputShutdown(); - + /** * Close any backing stream associated with the endpoint */ void close() throws IOException; /** - * Fill the buffer from the current putIndex to it's capacity from whatever + * Fill the buffer from the current putIndex to it's capacity from whatever * byte source is backing the buffer. The putIndex is increased if bytes filled. * The buffer may chose to do a compact before filling. - * @return an int value indicating the number of bytes + * @return an int value indicating the number of bytes * filled or -1 if EOF is reached. + * @throws EofException If input is shutdown or the endpoint is closed. */ int fill(Buffer buffer) throws IOException; - + /** * Flush the buffer from the current getIndex to it's putIndex using whatever byte * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. * Any mark set is cleared. * If the entire contents of the buffer are flushed, then an implicit empty() is done. - * + * * @param buffer The buffer to flush. This buffers getIndex is updated. * @return the number of bytes written + * @throws EofException If the endpoint is closed or output is shutdown. */ int flush(Buffer buffer) throws IOException; @@ -67,7 +69,7 @@ public interface EndPoint * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. * Any mark set is cleared. * If the entire contents of the buffer are flushed, then an implicit empty() is done. - * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done + * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done * either as gather writes, as a poke into this buffer or as several writes. The implementation is free to * select the optimal mechanism. * @param header A buffer to write before flushing this buffer. This buffers getIndex is updated. @@ -77,14 +79,14 @@ public interface EndPoint */ int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException; - + /* ------------------------------------------------------------ */ /** * @return The local IP address to which this EndPoint is bound, or null * if this EndPoint does not represent a network connection. */ public String getLocalAddr(); - + /* ------------------------------------------------------------ */ /** * @return The local host name to which this EndPoint is bound, or null @@ -120,13 +122,9 @@ public interface EndPoint */ public int getRemotePort(); - /* ------------------------------------------------------------ */ public boolean isBlocking(); - - /* ------------------------------------------------------------ */ - public boolean isBufferred(); - + /* ------------------------------------------------------------ */ public boolean blockReadable(long millisecs) throws IOException; @@ -141,27 +139,14 @@ public interface EndPoint * @return The underlying transport object (socket, channel, etc.) */ public Object getTransport(); - - /* ------------------------------------------------------------ */ - /** - * @return True if the endpoint has some buffered input data - */ - public boolean isBufferingInput(); - - /* ------------------------------------------------------------ */ - /** - * @return True if the endpoint has some buffered output data - */ - public boolean isBufferingOutput(); - + /* ------------------------------------------------------------ */ /** Flush any buffered output. * May fail to write all data if endpoint is non-blocking - * @throws IOException + * @throws EofException If the endpoint is closed or output is shutdown. */ public void flush() throws IOException; - - + /* ------------------------------------------------------------ */ /** Get the max idle time in ms. *

    The max idle time is the time the endpoint can be idle before @@ -172,14 +157,14 @@ public interface EndPoint * @return the max idle time in ms or if ms <= 0 implies an infinite timeout */ public int getMaxIdleTime(); - + /* ------------------------------------------------------------ */ /** Set the max idle time. * @param timeMs the max idle time in MS. Timeout <= 0 implies an infinite timeout * @throws IOException if the timeout cannot be set. */ public void setMaxIdleTime(int timeMs) throws IOException; - - - + + + } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java index 9c864aa078a..03369e5a79d 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EofException.java @@ -15,6 +15,13 @@ package org.eclipse.jetty.io; import java.io.EOFException; + +/* ------------------------------------------------------------ */ +/** A Jetty specialization of EOFException. + *

    This is thrown by Jetty to distinguish between EOF received from + * the connection, vs and EOF thrown by some application talking to some other file/socket etc. + * The only difference in handling is that Jetty EOFs are logged less verbosely. + */ public class EofException extends EOFException { public EofException() diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java b/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java index f114417e800..cfb05447a29 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java @@ -35,7 +35,8 @@ public class UncheckedPrintWriter extends PrintWriter private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class); private boolean _autoFlush = false; - private boolean _throwUnchecked=true; + private IOException _ioException; + private boolean _isClosed = false; /* ------------------------------------------------------------ */ /** @@ -102,38 +103,45 @@ public class UncheckedPrintWriter extends PrintWriter this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush); } + /* ------------------------------------------------------------ */ - private void setError(Throwable th) + public boolean checkError() { - setError(); - if (_throwUnchecked) - throw new RuntimeIOException(th); - LOG.debug(th); - } - - /* ------------------------------------------------------------ */ - /** Are unchecked exceptions thrown. - * @return True if {@link RuntimeIOException}s are thrown - */ - public boolean isUncheckedPrintWriter() - { - return _throwUnchecked; - } - - /* ------------------------------------------------------------ */ - /** Set if unchecked exceptions are thrown - * @param uncheckedPrintWriter True if {@link RuntimeIOException}s are to be thrown - */ - public void setUncheckedPrintWriter(boolean uncheckedPrintWriter) - { - _throwUnchecked = uncheckedPrintWriter; + return _ioException!=null || super.checkError(); } + /* ------------------------------------------------------------ */ + private void setError(Throwable th) + { + + super.setError(); + + if (th instanceof IOException) + _ioException=(IOException)th; + else + { + _ioException=new IOException(String.valueOf(th)); + _ioException.initCause(th); + } + + LOG.debug(th); + } + + + @Override + protected void setError() + { + setError(new IOException()); + } + /* ------------------------------------------------------------ */ /** Check to make sure that the stream has not been closed */ private void isOpen() throws IOException - { - if (super.out == null) + { + if (_ioException!=null) + throw new RuntimeIOException(_ioException); + + if (_isClosed) throw new IOException("Stream closed"); } @@ -170,6 +178,7 @@ public class UncheckedPrintWriter extends PrintWriter synchronized (lock) { out.close(); + _isClosed = true; } } catch (IOException ex) @@ -248,7 +257,7 @@ public class UncheckedPrintWriter extends PrintWriter */ @Override public void write(char buf[]) - { + { this.write(buf,0,buf.length); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java index e9e7d5e00ca..67fb8ab214f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io.bio; @@ -18,15 +18,12 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import javax.net.ssl.SSLSocket; + import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -/** - * - * To change the template for this generated type comment go to - * Window - Preferences - Java - Code Generation - Code and Comments - */ public class SocketEndPoint extends StreamEndPoint { private static final Logger LOG = Log.getLogger(SocketEndPoint.class); @@ -34,13 +31,13 @@ public class SocketEndPoint extends StreamEndPoint final Socket _socket; final InetSocketAddress _local; final InetSocketAddress _remote; - + /* ------------------------------------------------------------ */ /** - * + * */ public SocketEndPoint(Socket socket) - throws IOException + throws IOException { super(socket.getInputStream(),socket.getOutputStream()); _socket=socket; @@ -48,13 +45,13 @@ public class SocketEndPoint extends StreamEndPoint _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); super.setMaxIdleTime(_socket.getSoTimeout()); } - + /* ------------------------------------------------------------ */ /** - * + * */ protected SocketEndPoint(Socket socket, int maxIdleTime) - throws IOException + throws IOException { super(socket.getInputStream(),socket.getOutputStream()); _socket=socket; @@ -63,7 +60,7 @@ public class SocketEndPoint extends StreamEndPoint _socket.setSoTimeout(maxIdleTime>0?maxIdleTime:0); super.setMaxIdleTime(maxIdleTime); } - + /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.eclipse.io.BufferIO#isClosed() @@ -73,19 +70,39 @@ public class SocketEndPoint extends StreamEndPoint { return super.isOpen() && _socket!=null && !_socket.isClosed(); } - + /* ------------------------------------------------------------ */ @Override public boolean isInputShutdown() { - return !super.isOpen() || _socket!=null && _socket.isInputShutdown(); + if (_socket instanceof SSLSocket) + return super.isInputShutdown(); + return _socket.isClosed() || _socket.isInputShutdown(); } - + /* ------------------------------------------------------------ */ @Override public boolean isOutputShutdown() { - return !super.isOpen() || _socket!=null && _socket.isOutputShutdown(); + if (_socket instanceof SSLSocket) + return super.isOutputShutdown(); + + return _socket.isClosed() || _socket.isOutputShutdown(); + } + + + /* ------------------------------------------------------------ */ + /* + */ + protected final void shutdownSocketOutput() throws IOException + { + if (!_socket.isClosed()) + { + if (!_socket.isOutputShutdown()) + _socket.shutdownOutput(); + if (_socket.isInputShutdown()) + _socket.close(); + } } /* ------------------------------------------------------------ */ @@ -94,23 +111,41 @@ public class SocketEndPoint extends StreamEndPoint */ @Override public void shutdownOutput() throws IOException - { - if (!_socket.isClosed() && !_socket.isOutputShutdown()) - _socket.shutdownOutput(); + { + if (_socket instanceof SSLSocket) + super.shutdownOutput(); + else + shutdownSocketOutput(); } + /* ------------------------------------------------------------ */ + /* + */ + public void shutdownSocketInput() throws IOException + { + if (!_socket.isClosed()) + { + if (!_socket.isInputShutdown()) + _socket.shutdownInput(); + if (_socket.isOutputShutdown()) + _socket.close(); + } + } + /* ------------------------------------------------------------ */ /* * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput() */ @Override public void shutdownInput() throws IOException - { - if (!_socket.isClosed() && !_socket.isInputShutdown()) - _socket.shutdownInput(); + { + if (_socket instanceof SSLSocket) + super.shutdownInput(); + else + shutdownSocketInput(); } - + /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.eclipse.io.BufferIO#close() @@ -122,10 +157,10 @@ public class SocketEndPoint extends StreamEndPoint _in=null; _out=null; } - + /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalAddr() */ @Override @@ -133,12 +168,12 @@ public class SocketEndPoint extends StreamEndPoint { if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) return StringUtil.ALL_INTERFACES; - + return _local.getAddress().getHostAddress(); } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalHost() */ @Override @@ -146,12 +181,12 @@ public class SocketEndPoint extends StreamEndPoint { if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) return StringUtil.ALL_INTERFACES; - + return _local.getAddress().getCanonicalHostName(); } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalPort() */ @Override @@ -163,7 +198,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteAddr() */ @Override @@ -176,7 +211,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteHost() */ @Override @@ -188,7 +223,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemotePort() */ @Override @@ -200,7 +235,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getConnection() */ @Override @@ -237,5 +272,10 @@ public class SocketEndPoint extends StreamEndPoint _socket.close(); } } - + + @Override + public String toString() + { + return _local + " <--> " + _remote; + } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java index 51cec50a653..0ebab0058d3 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java @@ -22,17 +22,13 @@ import java.net.SocketTimeoutException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EndPoint; -/** - * - * - * To change the template for this generated type comment go to - * Window - Preferences - Java - Code Generation - Code and Comments - */ public class StreamEndPoint implements EndPoint { InputStream _in; OutputStream _out; int _maxIdleTime; + boolean _ishut; + boolean _oshut; /** * @@ -75,23 +71,29 @@ public class StreamEndPoint implements EndPoint } public void shutdownOutput() throws IOException - { + { + _oshut = true; + if (_ishut && _out!=null) + _out.close(); } - + public boolean isInputShutdown() { - return !isOpen(); + return _ishut; } public void shutdownInput() throws IOException - { + { + _ishut = true; + if (_oshut&&_in!=null) + _in.close(); } - + public boolean isOutputShutdown() { - return !isOpen(); + return _oshut; } - + /* * @see org.eclipse.io.BufferIO#close() */ @@ -107,35 +109,40 @@ public class StreamEndPoint implements EndPoint protected void idleExpired() throws IOException { - _in.close(); + if (_in!=null) + _in.close(); } - + /* (non-Javadoc) * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer) */ public int fill(Buffer buffer) throws IOException { - // TODO handle null array() + if (_ishut) + return -1; if (_in==null) return 0; - int space=buffer.space(); - if (space<=0) - { - if (buffer.hasContent()) - return 0; - throw new IOException("FULL"); - } + int space=buffer.space(); + if (space<=0) + { + if (buffer.hasContent()) + return 0; + throw new IOException("FULL"); + } - try - { - return buffer.readFrom(_in,space); - } - catch(SocketTimeoutException e) - { - idleExpired(); - return -1; - } + try + { + int filled=buffer.readFrom(_in, space); + if (filled<0) + shutdownInput(); + return filled; + } + catch(SocketTimeoutException e) + { + idleExpired(); + return -1; + } } /* (non-Javadoc) @@ -143,9 +150,10 @@ public class StreamEndPoint implements EndPoint */ public int flush(Buffer buffer) throws IOException { - // TODO handle null array() - if (_out==null) + if (_oshut) return -1; + if (_out==null) + return 0; int length=buffer.length(); if (length>0) buffer.writeTo(_out); @@ -297,30 +305,12 @@ public class StreamEndPoint implements EndPoint _out.flush(); } - /* ------------------------------------------------------------ */ - public boolean isBufferingInput() - { - return false; - } - - /* ------------------------------------------------------------ */ - public boolean isBufferingOutput() - { - return false; - } - - /* ------------------------------------------------------------ */ - public boolean isBufferred() - { - return false; - } - /* ------------------------------------------------------------ */ public int getMaxIdleTime() { return _maxIdleTime; } - + /* ------------------------------------------------------------ */ public void setMaxIdleTime(int timeMs) throws IOException { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java index 1e8aad30a26..1698249d438 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java @@ -53,6 +53,8 @@ public class StringEndPoint extends StreamEndPoint _in=_bin; _bout = new ByteArrayOutputStream(); _out=_bout; + _ishut=false; + _oshut=false; } catch(Exception e) { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java new file mode 100644 index 00000000000..717d01294ab --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java @@ -0,0 +1,23 @@ +// ======================================================================== +// Copyright (c) 2010 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.io.nio; + +import java.io.IOException; + +import org.eclipse.jetty.io.Connection; + +public interface AsyncConnection extends Connection +{ + void onInputShutdown() throws IOException; +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java index 3a8f2b18fbf..fb3c09145a8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.io.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.GatheringByteChannel; @@ -42,7 +43,9 @@ public class ChannelEndPoint implements EndPoint protected final Socket _socket; protected final InetSocketAddress _local; protected final InetSocketAddress _remote; - protected int _maxIdleTime; + protected volatile int _maxIdleTime; + private volatile boolean _ishut; + private volatile boolean _oshut; public ChannelEndPoint(ByteChannel channel) throws IOException { @@ -101,17 +104,76 @@ public class ChannelEndPoint implements EndPoint return _channel.isOpen(); } + /** Shutdown the channel Input. + * Cannot be overridden. To override, see {@link #shutdownInput()} + * @throws IOException + */ + protected final void shutdownChannelInput() throws IOException + { + LOG.debug("ishut {}", this); + _ishut = true; + if (_channel.isOpen()) + { + if (_socket != null) + { + try + { + if (!_socket.isInputShutdown()) + { + _socket.shutdownInput(); + } + } + catch (SocketException e) + { + LOG.debug(e.toString()); + LOG.ignore(e); + } + finally + { + if (_oshut) + { + close(); + } + } + } + } + } + /* (non-Javadoc) * @see org.eclipse.io.EndPoint#close() */ public void shutdownInput() throws IOException { - if (_channel.isOpen() && _channel instanceof SocketChannel) + shutdownChannelInput(); + } + + protected final void shutdownChannelOutput() throws IOException + { + LOG.debug("oshut {}",this); + _oshut = true; + if (_channel.isOpen()) { - Socket socket= ((SocketChannel)_channel).socket(); - if (!socket.isClosed()&&!socket.isInputShutdown()) + if (_socket != null) { - socket.shutdownInput(); + try + { + if (!_socket.isOutputShutdown()) + { + _socket.shutdownOutput(); + } + } + catch (SocketException e) + { + LOG.debug(e.toString()); + LOG.ignore(e); + } + finally + { + if (_ishut) + { + close(); + } + } } } } @@ -121,24 +183,17 @@ public class ChannelEndPoint implements EndPoint */ public void shutdownOutput() throws IOException { - if (_channel.isOpen() && _channel instanceof SocketChannel) - { - Socket socket= ((SocketChannel)_channel).socket(); - if (!socket.isClosed()&&!socket.isOutputShutdown()) - { - socket.shutdownOutput(); - } - } + shutdownChannelOutput(); } public boolean isOutputShutdown() { - return _channel.isOpen() && _socket!=null && _socket.isOutputShutdown(); + return _oshut || !_channel.isOpen() || _socket != null && _socket.isOutputShutdown(); } public boolean isInputShutdown() { - return _channel.isOpen() && _socket!=null && _socket.isInputShutdown(); + return _ishut || !_channel.isOpen() || _socket != null && _socket.isInputShutdown(); } /* (non-Javadoc) @@ -146,6 +201,7 @@ public class ChannelEndPoint implements EndPoint */ public void close() throws IOException { + LOG.debug("close {}",this); _channel.close(); } @@ -154,14 +210,16 @@ public class ChannelEndPoint implements EndPoint */ public int fill(Buffer buffer) throws IOException { + if (_ishut) + return -1; Buffer buf = buffer.buffer(); int len=0; if (buf instanceof NIOBuffer) { final NIOBuffer nbuf = (NIOBuffer)buf; final ByteBuffer bbuf=nbuf.getByteBuffer(); + //noinspection SynchronizationOnLocalVariableOrMethodParameter - try { synchronized(bbuf) @@ -178,25 +236,30 @@ public class ChannelEndPoint implements EndPoint } } - if (len<0 && isOpen() && !isInputShutdown()) + if (len<0 && isOpen()) { - shutdownInput(); + if (!isInputShutdown()) + shutdownInput(); + if (isOutputShutdown()) + _channel.close(); } } catch (IOException x) { + LOG.debug(x.toString()); + LOG.ignore(x); try { - close(); + if (_channel.isOpen()) + _channel.close(); } - catch (IOException xx) + catch (Exception xx) { LOG.ignore(xx); } - + if (len>0) throw x; - LOG.ignore(x); len=-1; } } @@ -204,7 +267,7 @@ public class ChannelEndPoint implements EndPoint { throw new IOException("Not Implemented"); } - + return len; } @@ -276,20 +339,6 @@ public class ChannelEndPoint implements EndPoint } else { - if (header!=null) - { - if (buffer!=null && buffer.length()>0 && header.space()>buffer.length()) - { - header.put(buffer); - buffer.clear(); - } - if (trailer!=null && trailer.length()>0 && header.space()>trailer.length()) - { - header.put(trailer); - trailer.clear(); - } - } - // flush header if (header!=null && header.length()>0) length=flush(header); @@ -461,24 +510,6 @@ public class ChannelEndPoint implements EndPoint { } - /* ------------------------------------------------------------ */ - public boolean isBufferingInput() - { - return false; - } - - /* ------------------------------------------------------------ */ - public boolean isBufferingOutput() - { - return false; - } - - /* ------------------------------------------------------------ */ - public boolean isBufferred() - { - return false; - } - /* ------------------------------------------------------------ */ public int getMaxIdleTime() { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java index fd5413ff3e0..ab5d9ff75c0 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout.Task; /* ------------------------------------------------------------ */ /** @@ -34,27 +35,50 @@ import org.eclipse.jetty.util.log.Logger; */ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint { - public static final Logger __log=Log.getLogger("org.eclipse.jetty.io.nio"); - + public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); + private final SelectorManager.SelectSet _selectSet; private final SelectorManager _manager; + private SelectionKey _key; private final Runnable _handler = new Runnable() { public void run() { handle(); } }; - private volatile Connection _connection; + /** The desired value for {@link SelectionKey#interestOps()} */ + private int _interestOps; + + /** + * The connection instance is the handler for any IO activity on the endpoint. + * There is a different type of connection for HTTP, AJP, WebSocket and + * ProxyConnect. The connection may change for an SCEP as it is upgraded + * from HTTP to proxy connect or websocket. + */ + private volatile AsyncConnection _connection; + + /** true if a thread has been dispatched to handle this endpoint */ private boolean _dispatched = false; - private boolean _redispatched = false; + + /** true if a non IO dispatch (eg async resume) is outstanding */ + private boolean _asyncDispatch = false; + + /** true if the last write operation succeed and wrote all offered bytes */ private volatile boolean _writable = true; - private SelectionKey _key; - private int _interestOps; + + /** True if a thread has is blocked in {@link #blockReadable(long)} */ private boolean _readBlocked; + + /** True if a thread has is blocked in {@link #blockWritable(long)} */ private boolean _writeBlocked; + + /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */ private boolean _open; + private volatile long _idleTimestamp; + private boolean _ishut; + /* ------------------------------------------------------------ */ public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime) throws IOException @@ -64,32 +88,13 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo _manager = selectSet.getManager(); _selectSet = selectSet; _dispatched = false; - _redispatched = false; + _asyncDispatch = false; _open=true; _key = key; - _connection = _manager.newConnection(channel,this); - - scheduleIdle(); + setCheckForIdle(true); } - /* ------------------------------------------------------------ */ - public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) - throws IOException - { - super(channel); - - _manager = selectSet.getManager(); - _selectSet = selectSet; - _dispatched = false; - _redispatched = false; - _open=true; - _key = key; - - _connection = _manager.newConnection(channel,this); - - scheduleIdle(); - } /* ------------------------------------------------------------ */ public SelectionKey getSelectionKey() { @@ -115,8 +120,9 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo public void setConnection(Connection connection) { Connection old=_connection; - _connection=connection; - _manager.endPointUpgraded(this,old); + _connection=(AsyncConnection)connection; + if (old!=null && old!=_connection) + _manager.endPointUpgraded(this,old); } /* ------------------------------------------------------------ */ @@ -124,7 +130,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { return _idleTimestamp; } - + /* ------------------------------------------------------------ */ /** Called by selectSet to schedule handling * @@ -154,17 +160,10 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo // wake them up is as good as a dispatched. this.notifyAll(); - // we are not interested in further selecting - if (_dispatched) - _key.interestOps(0); - return; - } - - // Otherwise if we are still dispatched - if (!isReadyForDispatch()) - { // we are not interested in further selecting _key.interestOps(0); + if (!_dispatched) + updateKey(); return; } @@ -189,6 +188,18 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } } + /* ------------------------------------------------------------ */ + public void asyncDispatch() + { + synchronized(this) + { + if (_dispatched) + _asyncDispatch=true; + else + dispatch(); + } + } + /* ------------------------------------------------------------ */ public void dispatch() { @@ -196,7 +207,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { if (_dispatched) { - _redispatched=true; + throw new IllegalStateException("dispatched"); } else { @@ -205,7 +216,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo if(!dispatched) { _dispatched = false; - __log.warn("Dispatched Failed! "+this+" to "+_manager); + LOG.warn("Dispatched Failed! "+this+" to "+_manager); updateKey(); } } @@ -223,9 +234,9 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { synchronized (this) { - if (_redispatched) + if (_asyncDispatch) { - _redispatched=false; + _asyncDispatch=false; return false; } _dispatched = false; @@ -235,39 +246,75 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } /* ------------------------------------------------------------ */ - public void scheduleIdle() + public void cancelTimeout(Task task) { - _idleTimestamp=System.currentTimeMillis(); + getSelectSet().cancelTimeout(task); } /* ------------------------------------------------------------ */ - public void cancelIdle() + public void scheduleTimeout(Task task, long timeoutMs) { - _idleTimestamp=0; + getSelectSet().scheduleTimeout(task,timeoutMs); + } + + /* ------------------------------------------------------------ */ + public void setCheckForIdle(boolean check) + { + _idleTimestamp=check?System.currentTimeMillis():0; + } + + /* ------------------------------------------------------------ */ + public boolean isCheckForIdle() + { + return _idleTimestamp!=0; + } + + /* ------------------------------------------------------------ */ + protected void notIdle() + { + if (_idleTimestamp!=0) + _idleTimestamp=System.currentTimeMillis(); } /* ------------------------------------------------------------ */ public void checkIdleTimestamp(long now) { long idleTimestamp=_idleTimestamp; - if (idleTimestamp!=0 && _maxIdleTime>0 && now>(idleTimestamp+_maxIdleTime)) - idleExpired(); + + if (idleTimestamp!=0 && _maxIdleTime>0) + { + long idleForMs=now-idleTimestamp; + + if (idleForMs>_maxIdleTime) + { + onIdleExpired(idleForMs); + _idleTimestamp=now; + } + } } /* ------------------------------------------------------------ */ - protected void idleExpired() + public void onIdleExpired(long idleForMs) { - _connection.idleExpired(); + _connection.onIdleExpired(idleForMs); + } + + /* ------------------------------------------------------------ */ + @Override + public int fill(Buffer buffer) throws IOException + { + int fill=super.fill(buffer); + if (fill>0) + notIdle(); + return fill; } /* ------------------------------------------------------------ */ - /* - */ @Override public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException { int l = super.flush(header, buffer, trailer); - + // If there was something to write and it wasn't written, then we are not writable. if (l==0 && ( header!=null && header.hasContent() || buffer!=null && buffer.hasContent() || trailer!=null && trailer.hasContent())) { @@ -278,8 +325,11 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo updateKey(); } } - else + else if (l>0) + { _writable=true; + notIdle(); + } return l; } @@ -290,7 +340,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo public int flush(Buffer buffer) throws IOException { int l = super.flush(buffer); - + // If there was something to write and it wasn't written, then we are not writable. if (l==0 && buffer!=null && buffer.hasContent()) { @@ -301,20 +351,13 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo updateKey(); } } - else - _writable=true; - - return l; - } - - /* ------------------------------------------------------------ */ - public boolean isReadyForDispatch() - { - synchronized (this) + else if (l>0) { - // Ready if not dispatched and not suspended - return !(_dispatched || getConnection().isSuspended()); + _writable=true; + notIdle(); } + + return l; } /* ------------------------------------------------------------ */ @@ -326,12 +369,17 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { synchronized (this) { + if (isInputShutdown()) + throw new EofException(); + long now=_selectSet.getNow(); long end=now+timeoutMs; + boolean check=isCheckForIdle(); + setCheckForIdle(true); try { _readBlocked=true; - while (isOpen() && _readBlocked) + while (!isInputShutdown() && _readBlocked) { try { @@ -340,7 +388,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (InterruptedException e) { - __log.warn(e); + LOG.warn(e); } finally { @@ -354,6 +402,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo finally { _readBlocked=false; + setCheckForIdle(check); } } return true; @@ -368,15 +417,17 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { synchronized (this) { - if (!isOpen() || isOutputShutdown()) + if (isOutputShutdown()) throw new EofException(); - + long now=_selectSet.getNow(); long end=now+timeoutMs; + boolean check=isCheckForIdle(); + setCheckForIdle(true); try { _writeBlocked=true; - while (isOpen() && _writeBlocked && !isOutputShutdown()) + while (_writeBlocked && !isOutputShutdown()) { try { @@ -385,7 +436,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (InterruptedException e) { - __log.warn(e); + LOG.warn(e); } finally { @@ -395,33 +446,47 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo return false; } } - catch(Throwable e) - { - // TODO remove this if it finds nothing - __log.warn(e); - if (e instanceof RuntimeException) - throw (RuntimeException)e; - if (e instanceof Error) - throw (Error)e; - throw new RuntimeException(e); - } finally { _writeBlocked=false; - if (_idleTimestamp!=-1) - scheduleIdle(); + setCheckForIdle(check); } } return true; } - + /* ------------------------------------------------------------ */ - public void scheduleWrite() + /* short cut for busyselectChannelServerTest */ + public void clearWritable() { _writable=false; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite() + */ + public void scheduleWrite() + { + if (_writable==true) + LOG.debug("Required scheduleWrite {}",this); + + _writable=false; updateKey(); } + /* ------------------------------------------------------------ */ + public boolean isWritable() + { + return _writable; + } + + /* ------------------------------------------------------------ */ + public boolean hasProgressed() + { + return false; + } + /* ------------------------------------------------------------ */ /** * Updates selection key. Adds operations types to the selection key as needed. No operations @@ -430,32 +495,39 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo */ private void updateKey() { + final boolean changed; synchronized (this) { - int ops=-1; + int current_ops=-1; if (getChannel().isOpen()) { + boolean read_interest = _readBlocked || (!_dispatched && !_connection.isSuspended()); + boolean write_interest= _writeBlocked || (!_dispatched && !_writable); + _interestOps = - ((!_socket.isInputShutdown() && (!_dispatched || _readBlocked)) ? SelectionKey.OP_READ : 0) - | ((!_socket.isOutputShutdown()&& (!_writable || _writeBlocked)) ? SelectionKey.OP_WRITE : 0); + ((!_socket.isInputShutdown() && read_interest ) ? SelectionKey.OP_READ : 0) + | ((!_socket.isOutputShutdown()&& write_interest) ? SelectionKey.OP_WRITE : 0); try { - ops = ((_key!=null && _key.isValid())?_key.interestOps():-1); + current_ops = ((_key!=null && _key.isValid())?_key.interestOps():-1); } catch(Exception e) { _key=null; - __log.ignore(e); + LOG.ignore(e); } } - - if(_interestOps == ops && getChannel().isOpen()) - return; + changed=_interestOps!=current_ops; + } + + if(changed) + { + _selectSet.addChange(this); + _selectSet.wakeup(); } - _selectSet.addChange(this); - _selectSet.wakeup(); } + /* ------------------------------------------------------------ */ /** * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey @@ -483,12 +555,11 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (Exception e) { - __log.ignore(e); + LOG.ignore(e); if (_key!=null && _key.isValid()) { _key.cancel(); } - cancelIdle(); if (_open) { @@ -517,12 +588,11 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo if (_key!=null && _key.isValid()) _key.cancel(); - cancelIdle(); if (_open) { + _open=false; _selectSet.destroyEndPoint(this); } - _open=false; _key = null; } } @@ -542,11 +612,13 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { while(true) { - final Connection next = _connection.handle(); + final AsyncConnection next = (AsyncConnection)_connection.handle(); if (next!=_connection) { - __log.debug("{} replaced {}",next,_connection); + LOG.debug("{} replaced {}",next,_connection); + Connection old=_connection; _connection=next; + _manager.endPointUpgraded(this,old); continue; } break; @@ -554,28 +626,49 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (ClosedChannelException e) { - __log.ignore(e); + LOG.ignore(e); } catch (EofException e) { - __log.debug("EOF", e); + LOG.debug("EOF", e); try{close();} - catch(IOException e2){__log.ignore(e2);} + catch(IOException e2){LOG.ignore(e2);} } catch (IOException e) { - __log.warn(e.toString()); - __log.debug(e); + LOG.warn(e.toString()); + LOG.debug(e); try{close();} - catch(IOException e2){__log.ignore(e2);} + catch(IOException e2){LOG.ignore(e2);} } catch (Throwable e) { - __log.warn("handle failed", e); + LOG.warn("handle failed", e); try{close();} - catch(IOException e2){__log.ignore(e2);} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + if (!_ishut && isInputShutdown() && isOpen()) + { + _ishut=true; + try + { + _connection.onInputShutdown(); + } + catch(Throwable x) + { + LOG.warn("onInputShutdown failed", x); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + updateKey(); + } + } + dispatched=!undispatch(); } - dispatched=!undispatch(); } } finally @@ -585,7 +678,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo dispatched=!undispatch(); while (dispatched) { - __log.warn("SCEP.run() finally DISPATCHED"); + LOG.warn("SCEP.run() finally DISPATCHED"); dispatched=!undispatch(); } } @@ -605,7 +698,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (IOException e) { - __log.ignore(e); + LOG.ignore(e); } finally { @@ -617,12 +710,43 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo @Override public String toString() { - synchronized(this) + // Do NOT use synchronized (this) + // because it's very easy to deadlock when debugging is enabled. + // We do a best effort to print the right toString() and that's it. + SelectionKey key = _key; + String keyString = ""; + if (key != null) { - return "SCEP@" + hashCode() + _channel+ - "[d=" + _dispatched + ",io=" + _interestOps+ - ",w=" + _writable + ",rb=" + _readBlocked + ",wb=" + _writeBlocked + "]"; + if (key.isValid()) + { + if (key.isReadable()) + keyString += "r"; + if (key.isWritable()) + keyString += "w"; + } + else + { + keyString += "!"; + } } + else + { + keyString += "-"; + } + return String.format("SCEP@%x{l(%s)<->r(%s),d=%b,open=%b,ishut=%b,oshut=%b,rb=%b,wb=%b,w=%b,i=%d%s}-{%s}", + hashCode(), + _socket.getRemoteSocketAddress(), + _socket.getLocalSocketAddress(), + _dispatched, + isOpen(), + isInputShutdown(), + isOutputShutdown(), + _readBlocked, + _writeBlocked, + _writable, + _interestOps, + keyString, + _connection); } /* ------------------------------------------------------------ */ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java index 9ca19c3956e..eaeabc88624 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io.nio; @@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -49,30 +49,25 @@ import org.eclipse.jetty.util.thread.Timeout.Task; * The Selector Manager manages and number of SelectSets to allow * NIO scheduling to scale to large numbers of connections. *

    - * This class works around a number of know JVM bugs. For details - * see http://wiki.eclipse.org/Jetty/Feature/JVM_NIO_Bug */ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable { public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); - - // TODO Tune these by approx system speed. - private static final int __JVMBUG_THRESHHOLD=Integer.getInteger("org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD",0).intValue(); + private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue(); - private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",25000).intValue(); + private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",100000).intValue(); private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue(); - private static final int __BUSY_KEY=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_KEY",-1).intValue(); private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue(); - + private int _maxIdleTime; private int _lowResourcesMaxIdleTime; private long _lowResourcesConnections; private SelectSet[] _selectSet; private int _selectSets=1; - private volatile int _set; + private volatile int _set=0; private boolean _deferringInterestedOps0=true; private int _selectorPriorityDelta=0; - + /* ------------------------------------------------------------ */ /** * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed. @@ -82,18 +77,18 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _maxIdleTime=(int)maxIdleTime; } - + /* ------------------------------------------------------------ */ /** * @param selectSets number of select sets to create */ public void setSelectSets(int selectSets) { - long lrc = _lowResourcesConnections * _selectSets; + long lrc = _lowResourcesConnections * _selectSets; _selectSets=selectSets; _lowResourcesConnections=lrc/_selectSets; } - + /* ------------------------------------------------------------ */ /** * @return the max idle time @@ -102,7 +97,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { return _maxIdleTime; } - + /* ------------------------------------------------------------ */ /** * @return the number of select sets in use @@ -114,14 +109,14 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ /** - * @param i + * @param i * @return The select set */ public SelectSet getSelectSet(int i) { return _selectSet[i]; } - + /* ------------------------------------------------------------ */ /** Register a channel * @param channel @@ -132,8 +127,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // The ++ increment here is not atomic, but it does not matter. // so long as the value changes sometimes, then connections will // be distributed over the available sets. - - int s=_set++; + + int s=_set++; + if (s<0) + s=-s; s=s%_selectSets; SelectSet[] sets=_selectSet; if (sets!=null) @@ -144,7 +141,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } - + /* ------------------------------------------------------------ */ /** Register a channel * @param channel @@ -154,8 +151,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // The ++ increment here is not atomic, but it does not matter. // so long as the value changes sometimes, then connections will // be distributed over the available sets. - - int s=_set++; + + int s=_set++; + if (s<0) + s=-s; s=s%_selectSets; SelectSet[] sets=_selectSet; if (sets!=null) @@ -165,14 +164,16 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa set.wakeup(); } } - + /* ------------------------------------------------------------ */ /** Register a {@link ServerSocketChannel} * @param acceptChannel */ public void register(ServerSocketChannel acceptChannel) { - int s=_set++; + int s=_set++; + if (s<0) + s=-s; s=s%_selectSets; SelectSet set=_selectSet[s]; set.addChange(acceptChannel); @@ -196,8 +197,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _selectorPriorityDelta=delta; } - - + + /* ------------------------------------------------------------ */ /** * @return the lowResourcesConnections @@ -237,7 +238,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _lowResourcesMaxIdleTime=(int)lowResourcesMaxIdleTime; } - + /* ------------------------------------------------------------------------------- */ public abstract boolean dispatch(Runnable task); @@ -254,7 +255,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _selectSet[i]= new SelectSet(i); super.doStart(); - + // start a thread to Select for (int i=0;i _changes = new ConcurrentLinkedQueue(); - - private Selector _selector; - + + private volatile Selector _selector; + private volatile Thread _selecting; - private int _jvmBug; - private int _selects; - private long _monitorStart; + private int _busySelects; private long _monitorNext; private boolean _pausing; - private SelectionKey _busyKey; - private int _busyKeyCount; - private long _log; - private int _paused; - private int _jvmFix0; - private int _jvmFix1; - private int _jvmFix2; + private boolean _paused; private volatile long _idleTick; private ConcurrentMap _endPoints = new ConcurrentHashMap(); - + /* ------------------------------------------------------------ */ SelectSet(int acceptorID) throws Exception { @@ -416,11 +405,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // create a selector; _selector = Selector.open(); - _monitorStart=System.currentTimeMillis(); - _monitorNext=_monitorStart+__MONITOR_PERIOD; - _log=_monitorStart+60000; + _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD; } - + /* ------------------------------------------------------------ */ public void addChange(Object change) { @@ -429,7 +416,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ public void addChange(SelectableChannel channel, Object att) - { + { if (att==null) addChange(channel); else if (att instanceof EndPoint) @@ -437,11 +424,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa else addChange(new ChannelAndAttachment(channel,att)); } - + /* ------------------------------------------------------------ */ /** * Select and dispatch tasks found from changes and the selector. - * + * * @throws IOException */ public void doSelect() throws IOException @@ -450,6 +437,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _selecting=Thread.currentThread(); final Selector selector=_selector; + // Stopped concurrently ? + if (selector == null) + return; // Make any key changes required Object change; @@ -458,7 +448,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { Channel ch=null; SelectionKey key=null; - + try { if (change instanceof EndPoint) @@ -475,7 +465,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa final SelectableChannel channel=asc._channel; ch=channel; final Object att = asc._attachment; - + if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected()) { key = channel.register(selector,SelectionKey.OP_READ,att); @@ -515,9 +505,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } catch (Throwable e) { - if (e instanceof ThreadDeath) - throw (ThreadDeath)e; - if (isRunning()) LOG.warn(e); else @@ -525,7 +512,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa try { - ch.close(); + if (ch!=null) + ch.close(); } catch(IOException e2) { @@ -537,10 +525,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // Do and instant select to see if any connections can be handled. int selected=selector.selectNow(); - _selects++; long now=System.currentTimeMillis(); - + // if no immediate things to do if (selected==0 && selector.selectedKeys().isEmpty()) { @@ -562,7 +549,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _timeout.setNow(now); long to_next_timeout=_timeout.getTimeToNext(); - long wait = _changes.size()==0?__IDLE_TICK:0L; + long wait = _changes.size()==0?__IDLE_TICK:0L; if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout) wait = to_next_timeout; @@ -571,24 +558,40 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { long before=now; selected=selector.select(wait); - _selects++; now = System.currentTimeMillis(); _timeout.setNow(now); - - if (__JVMBUG_THRESHHOLD>0) - checkJvmBugs(before, now, wait, selected); + + // If we are monitoring for busy selector + // and this select did not wait more than 1ms + if (__MONITOR_PERIOD>0 && now-before <=1) + { + // count this as a busy select and if there have been too many this monitor cycle + if (++_busySelects>__MAX_SELECTS) + { + // Start injecting pauses + _pausing=true; + + // if this is the first pause + if (!_paused) + { + // Log and dump some status + _paused=true; + LOG.warn("Selector {} is too busy, pausing!",this); + } + } + } } } - + // have we been destroyed while sleeping if (_selector==null || !selector.isOpen()) return; // Look for things to do for (SelectionKey key: selector.selectedKeys()) - { + { SocketChannel channel=null; - + try { if (!key.isValid()) @@ -641,7 +644,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa SelectChannelEndPoint endpoint = createEndPoint(channel,key); key.attach(endpoint); if (key.isReadable()) - endpoint.schedule(); + endpoint.schedule(); } key = null; } @@ -665,15 +668,15 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { LOG.debug(e2); } - + if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid()) key.cancel(); } } - + // Everything always handled selector.selectedKeys().clear(); - + now=System.currentTimeMillis(); _timeout.setNow(now); Task task = _timeout.expired(); @@ -688,11 +691,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa if (now-_idleTick>__IDLE_TICK) { _idleTick=now; - + final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections)) ?(now+_maxIdleTime-_lowResourcesMaxIdleTime) :now; - + dispatch(new Runnable() { public void run() @@ -702,7 +705,18 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa endp.checkIdleTimestamp(idle_now); } } + public String toString() {return "Idle-"+super.toString();} }); + + } + + // Reset busy select monitor counts + if (__MONITOR_PERIOD>0 && now>_monitorNext) + { + _busySelects=0; + _pausing=false; + _monitorNext=now+__MONITOR_PERIOD; + } } catch (ClosedSelectorException e) @@ -721,130 +735,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _selecting=null; } } - + + /* ------------------------------------------------------------ */ - private void checkJvmBugs(long before, long now, long wait, int selected) - throws IOException - { - Selector selector = _selector; - if (selector==null) - return; - - // Look for JVM bugs over a monitor period. - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 - // http://bugs.sun.com/view_bug.do?bug_id=6693490 - if (now>_monitorNext) - { - _selects=(int)(_selects*__MONITOR_PERIOD/(now-_monitorStart)); - _pausing=_selects>__MAX_SELECTS; - if (_pausing) - _paused++; - - _selects=0; - _jvmBug=0; - _monitorStart=now; - _monitorNext=now+__MONITOR_PERIOD; - } - - if (now>_log) - { - if (_paused>0) - LOG.debug(this+" Busy selector - injecting delay "+_paused+" times"); - - if (_jvmFix2>0) - LOG.debug(this+" JVM BUG(s) - injecting delay"+_jvmFix2+" times"); - - if (_jvmFix1>0) - LOG.debug(this+" JVM BUG(s) - recreating selector "+_jvmFix1+" times, cancelled keys "+_jvmFix0+" times"); - - else if(LOG.isDebugEnabled() && _jvmFix0>0) - LOG.debug(this+" JVM BUG(s) - cancelled keys "+_jvmFix0+" times"); - _paused=0; - _jvmFix2=0; - _jvmFix1=0; - _jvmFix0=0; - _log=now+60000; - } - - // If we see signature of possible JVM bug, increment count. - if (selected==0 && wait>10 && (now-before)<(wait/2)) - { - // Increment bug count and try a work around - _jvmBug++; - if (_jvmBug>(__JVMBUG_THRESHHOLD)) - { - try - { - if (_jvmBug==__JVMBUG_THRESHHOLD+1) - _jvmFix2++; - - Thread.sleep(__BUSY_PAUSE); // pause to avoid busy loop - } - catch(InterruptedException e) - { - LOG.ignore(e); - } - } - else if (_jvmBug==__JVMBUG_THRESHHOLD) - { - renewSelector(); - } - else if (_jvmBug%32==31) // heuristic attempt to cancel key 31,63,95,... loops - { - // Cancel keys with 0 interested ops - int cancelled=0; - for (SelectionKey k: selector.keys()) - { - if (k.isValid()&&k.interestOps()==0) - { - k.cancel(); - cancelled++; - } - } - if (cancelled>0) - _jvmFix0++; - - return; - } - } - else if (__BUSY_KEY>0 && selected==1 && _selects>__MAX_SELECTS) - { - // Look for busy key - SelectionKey busy = selector.selectedKeys().iterator().next(); - if (busy==_busyKey) - { - if (++_busyKeyCount>__BUSY_KEY && !(busy.channel() instanceof ServerSocketChannel)) - { - final SelectChannelEndPoint endpoint = (SelectChannelEndPoint)busy.attachment(); - LOG.warn("Busy Key "+busy.channel()+" "+endpoint); - busy.cancel(); - if (endpoint!=null) - { - dispatch(new Runnable() - { - public void run() - { - try - { - endpoint.close(); - } - catch (IOException e) - { - LOG.ignore(e); - } - } - }); - } - } - } - else - _busyKeyCount=0; - _busyKey=busy; - } - } - - /* ------------------------------------------------------------ */ - private void renewSelector() + private void renewSelector() { try { @@ -876,7 +770,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa throw new RuntimeException("recreating selector",e); } } - + /* ------------------------------------------------------------ */ public SelectorManager getManager() { @@ -891,9 +785,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ /** - * @param task The task to timeout. If it implements Runnable, then + * @param task The task to timeout. If it implements Runnable, then * expired will be called from a dispatched thread. - * + * * @param timeoutMs */ public void scheduleTimeout(Timeout.Task task, long timeoutMs) @@ -902,7 +796,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa throw new IllegalArgumentException("!Runnable"); _timeout.schedule(task, timeoutMs); } - + /* ------------------------------------------------------------ */ public void cancelTimeout(Timeout.Task task) { @@ -927,23 +821,25 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa renewSelector(); } }); - + renewSelector(); } } - + /* ------------------------------------------------------------ */ private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException { SelectChannelEndPoint endp = newEndPoint(channel,this,sKey); - endPointOpened(endp); + LOG.debug("created {}",endp); + endPointOpened(endp); _endPoints.put(endp,this); return endp; } - + /* ------------------------------------------------------------ */ public void destroyEndPoint(SelectChannelEndPoint endp) { + LOG.debug("destroyEndPoint {}",endp); _endPoints.remove(endp); endPointClosed(endp); } @@ -953,11 +849,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { return _selector; } - + /* ------------------------------------------------------------ */ void stop() throws Exception { - // Spin for a while waiting for selector to complete + // Spin for a while waiting for selector to complete // to avoid unneccessary closed channel exceptions try { @@ -975,7 +871,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // close endpoints and selector synchronized (this) { - for (SelectionKey key:_selector.keys()) + Selector selector=_selector; + for (SelectionKey key:selector.keys()) { if (key==null) continue; @@ -993,18 +890,19 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } } - - + + _timeout.cancelAll(); try { - if (_selector != null) - _selector.close(); + selector=_selector; + if (selector != null) + selector.close(); } catch (IOException e) { LOG.ignore(e); - } + } _selector=null; } } @@ -1019,9 +917,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa public void dump(Appendable out, String indent) throws IOException { out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n"); - + Thread selecting = _selecting; - + Object where = "not selecting"; StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace(); if (trace!=null) @@ -1035,28 +933,32 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } Selector selector=_selector; - final ArrayList dump = new ArrayList(selector.keys().size()*2); - dump.add(where); - - final CountDownLatch latch = new CountDownLatch(1); - - addChange(new Runnable(){ - public void run() + if (selector!=null) + { + final ArrayList dump = new ArrayList(selector.keys().size()*2); + dump.add(where); + + final CountDownLatch latch = new CountDownLatch(1); + + addChange(new ChangeTask() { - dumpKeyState(dump); - latch.countDown(); + public void run() + { + dumpKeyState(dump); + latch.countDown(); + } + }); + + try + { + latch.await(5,TimeUnit.SECONDS); } - }); - - try - { - latch.await(5,TimeUnit.SECONDS); + catch(InterruptedException e) + { + LOG.ignore(e); + } + AggregateLifeCycle.dump(out,indent,dump); } - catch(InterruptedException e) - { - LOG.ignore(e); - } - AggregateLifeCycle.dump(out,indent,dump); } /* ------------------------------------------------------------ */ @@ -1067,11 +969,22 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa for (SelectionKey key: selector.keys()) { if (key.isValid()) - dumpto.add(key.attachment()+" "+key.interestOps()+" "+key.readyOps()); + dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps()); else - dumpto.add(key.attachment()+" - - "); + dumpto.add(key.attachment()+" iOps=-1 rOps=-1"); } } + + /* ------------------------------------------------------------ */ + public String toString() + { + Selector selector=_selector; + return String.format("%s %s keys=%d selected=%d", + super.toString(), + SelectorManager.this.getState(), + selector != null && selector.isOpen() ? selector.keys().size() : -1, + selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1); + } } /* ------------------------------------------------------------ */ @@ -1079,7 +992,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { final SelectableChannel _channel; final Object _attachment; - + public ChannelAndAttachment(SelectableChannel channel, Object attachment) { super(); @@ -1099,12 +1012,12 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _deferringInterestedOps0 = deferringInterestedOps0; } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ private interface ChangeTask extends Runnable {} - + } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java new file mode 100644 index 00000000000..876db22112d --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java @@ -0,0 +1,834 @@ +// ======================================================================== +// Copyright (c) 2004-2011 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.io.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +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.SSLSession; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout.Task; + +/* ------------------------------------------------------------ */ +/** SSL Connection. + * An AysyncConnection that acts as an interceptor between and EndPoint and another + * Connection, that implements TLS encryption using an {@link SSLEngine}. + *

    + * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as + * it's source/sink of encrypted data. It then provides {@link #getSslEndPoint()} to + * expose a source/sink of unencrypted data to another connection (eg HttpConnection). + */ +public class SslConnection extends AbstractConnection implements AsyncConnection +{ + static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio.ssl"); + + private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0); + + private static final ThreadLocal __buffers = new ThreadLocal(); + private final SSLEngine _engine; + private final SSLSession _session; + private AsyncConnection _connection; + private final SslEndPoint _sslEndPoint; + private int _allocations; + private SslBuffers _buffers; + private NIOBuffer _inbound; + private NIOBuffer _unwrapBuf; + private NIOBuffer _outbound; + private AsyncEndPoint _aEndp; + private boolean _allowRenegotiate=true; + private boolean _handshook; + private boolean _ishut; + private boolean _oshut; + private final AtomicBoolean _progressed = new AtomicBoolean(); + + /* ------------------------------------------------------------ */ + /* this is a half baked buffer pool + */ + private static class SslBuffers + { + final NIOBuffer _in; + final NIOBuffer _out; + final NIOBuffer _unwrap; + + SslBuffers(int packetSize, int appSize) + { + _in=new IndirectNIOBuffer(packetSize); + _out=new IndirectNIOBuffer(packetSize); + _unwrap=new IndirectNIOBuffer(appSize); + } + } + + /* ------------------------------------------------------------ */ + public SslConnection(SSLEngine engine,EndPoint endp) + { + this(engine,endp,System.currentTimeMillis()); + } + + /* ------------------------------------------------------------ */ + public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp) + { + super(endp,timeStamp); + _engine=engine; + _session=_engine.getSession(); + _aEndp=(AsyncEndPoint)endp; + _sslEndPoint = newSslEndPoint(); + } + + /* ------------------------------------------------------------ */ + protected SslEndPoint newSslEndPoint() + { + return new SslEndPoint(); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban + * of renegotiates in u19 and with RFC5746 in u22. + * + * @param allowRenegotiate + * true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + _allowRenegotiate = allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + private void allocateBuffers() + { + synchronized (this) + { + if (_allocations++==0) + { + if (_buffers==null) + { + _buffers=__buffers.get(); + if (_buffers==null) + _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2); + _inbound=_buffers._in; + _outbound=_buffers._out; + _unwrapBuf=_buffers._unwrap; + __buffers.set(null); + } + } + } + } + + /* ------------------------------------------------------------ */ + private void releaseBuffers() + { + synchronized (this) + { + if (--_allocations==0) + { + if (_buffers!=null && + _inbound.length()==0 && + _outbound.length()==0 && + _unwrapBuf.length()==0) + { + _inbound=null; + _outbound=null; + _unwrapBuf=null; + __buffers.set(_buffers); + _buffers=null; + } + } + } + } + + /* ------------------------------------------------------------ */ + public Connection handle() throws IOException + { + try + { + allocateBuffers(); + + boolean progress=true; + + while (progress) + { + progress=false; + + // If we are handshook let the delegate connection + if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING) + { + progress=process(null,null); + } + else + { + // handle the delegate connection + AsyncConnection next = (AsyncConnection)_connection.handle(); + if (next!=_connection && next!=null) + { + _connection=next; + progress=true; + } + // TODO: consider moving here hasProgressed() - it's only used in SSL + } + + LOG.debug("{} handle {} progress={}", _session, this, progress); + } + } + finally + { + releaseBuffers(); + + if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen()) + { + _ishut=true; + try + { + _connection.onInputShutdown(); + } + catch(Throwable x) + { + LOG.warn("onInputShutdown failed", x); + try{_sslEndPoint.close();} + catch(IOException e2){LOG.ignore(e2);} + } + } + } + + return this; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isSuspended() + { + return false; + } + + /* ------------------------------------------------------------ */ + public void onClose() + { + } + + /* ------------------------------------------------------------ */ + @Override + public void onIdleExpired(long idleForMs) + { + try + { + LOG.debug("onIdleExpired {}ms on {}",idleForMs,this); + if (_endp.isOutputShutdown()) + _sslEndPoint.close(); + else + _sslEndPoint.shutdownOutput(); + } + catch (IOException e) + { + LOG.warn(e); + super.onIdleExpired(idleForMs); + } + } + + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + + } + + /* ------------------------------------------------------------ */ + private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException + { + boolean some_progress=false; + try + { + allocateBuffers(); + if (toFill==null) + { + _unwrapBuf.compact(); + toFill=_unwrapBuf; + } + else if (toFill.capacity()<_session.getApplicationBufferSize()) + { + boolean progress=process(null,toFlush); + if (_unwrapBuf!=null && _unwrapBuf.hasContent()) + { + _unwrapBuf.skip(toFill.put(_unwrapBuf)); + return true; + } + else + return progress; + } + else if (_unwrapBuf!=null && _unwrapBuf.hasContent()) + { + _unwrapBuf.skip(toFill.put(_unwrapBuf)); + return true; + } + + + if (toFlush==null) + toFlush=__ZERO_BUFFER; + + boolean progress=true; + + while (progress) + { + progress=false; + int filled=0,flushed=0; + + try + { + // Read any available data + if (_inbound.space()>0 && (filled=_endp.fill(_inbound))>0) + progress = true; + + // flush any output data + if (_outbound.hasContent() && (flushed=_endp.flush(_outbound))>0) + progress = true; + } + catch (IOException e) + { + _endp.close(); + throw e; + } + finally + { + LOG.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length()); + } + + // handle the current hand share status + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + throw new IllegalStateException(); + + case NOT_HANDSHAKING: + { + // Try unwrapping some application data + if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill)) + progress=true; + + // Try wrapping some application data + if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush)) + progress=true; + } + break; + + case NEED_TASK: + { + // A task needs to be run, so run it! + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + progress=true; + task.run(); + } + + // Detect SUN JVM Bug!!! + /* TODO + if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && + _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && sent==0 ) + { + // This should be NEED_WRAP + // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. + // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. + // See http://jira.codehaus.org/browse/JETTY-567 for more details + LOG.warn("{} JETTY-567",_session); + _endp.close(); + return false; + } + */ + } + break; + + case NEED_WRAP: + { + // The SSL needs to send some handshake data to the other side + if (_handshook && !_allowRenegotiate) + _endp.close(); + else if (wrap(toFlush)) + progress=true; + } + break; + + case NEED_UNWRAP: + { + // The SSL needs to receive some handshake data from the other side + if (_handshook && !_allowRenegotiate) + _endp.close(); + else if (unwrap(toFill)) + progress=true; + } + break; + } + + // pass on ishut/oshut state + if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent()) + _engine.closeInbound(); + + if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent()) + _endp.shutdownOutput(); + + some_progress|=progress; + } + + if (toFill.hasContent()) + _aEndp.asyncDispatch(); + } + finally + { + releaseBuffers(); + if (some_progress) + _progressed.set(true); + } + return some_progress; + } + + private synchronized boolean wrap(final Buffer buffer) throws IOException + { + ByteBuffer bbuf=extractByteBuffer(buffer); + final SSLEngineResult result; + + synchronized(bbuf) + { + _outbound.compact(); + ByteBuffer out_buffer=_outbound.getByteBuffer(); + synchronized(out_buffer) + { + try + { + bbuf.position(buffer.getIndex()); + bbuf.limit(buffer.putIndex()); + out_buffer.position(_outbound.putIndex()); + out_buffer.limit(out_buffer.capacity()); + result=_engine.wrap(bbuf,out_buffer); + if (LOG.isDebugEnabled()) + LOG.debug("{} wrap {} {} consumed={} produced={}", + _session, + result.getStatus(), + result.getHandshakeStatus(), + result.bytesConsumed(), + result.bytesProduced()); + + + buffer.skip(result.bytesConsumed()); + _outbound.setPutIndex(_outbound.putIndex()+result.bytesProduced()); + } + catch(SSLException e) + { + LOG.warn(String.valueOf(_endp), e); + _endp.close(); + throw e; + } + finally + { + out_buffer.position(0); + out_buffer.limit(out_buffer.capacity()); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); + } + } + } + + switch(result.getStatus()) + { + case BUFFER_UNDERFLOW: + throw new IllegalStateException(); + + case BUFFER_OVERFLOW: + break; + + case OK: + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _handshook=true; + break; + + case CLOSED: + LOG.debug("wrap CLOSE {} {}",this,result); + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _endp.close(); + break; + + default: + LOG.warn("{} wrap default {}",_session,result); + throw new IOException(result.toString()); + } + + return result.bytesConsumed()>0 || result.bytesProduced()>0; + } + + private synchronized boolean unwrap(final Buffer buffer) throws IOException + { + if (!_inbound.hasContent()) + return false; + + ByteBuffer bbuf=extractByteBuffer(buffer); + final SSLEngineResult result; + + synchronized(bbuf) + { + ByteBuffer in_buffer=_inbound.getByteBuffer(); + synchronized(in_buffer) + { + try + { + bbuf.position(buffer.putIndex()); + bbuf.limit(buffer.capacity()); + in_buffer.position(_inbound.getIndex()); + in_buffer.limit(_inbound.putIndex()); + + result=_engine.unwrap(in_buffer,bbuf); + if (LOG.isDebugEnabled()) + LOG.debug("{} unwrap {} {} consumed={} produced={}", + _session, + result.getStatus(), + result.getHandshakeStatus(), + result.bytesConsumed(), + result.bytesProduced()); + + _inbound.skip(result.bytesConsumed()); + _inbound.compact(); + buffer.setPutIndex(buffer.putIndex()+result.bytesProduced()); + } + catch(SSLException e) + { + LOG.warn(String.valueOf(_endp), e); + _endp.close(); + throw e; + } + finally + { + in_buffer.position(0); + in_buffer.limit(in_buffer.capacity()); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); + } + } + } + + switch(result.getStatus()) + { + case BUFFER_UNDERFLOW: + break; + + case BUFFER_OVERFLOW: + LOG.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString()); + break; + + case OK: + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _handshook=true; + break; + + case CLOSED: + LOG.debug("unwrap CLOSE {} {}",this,result); + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _endp.close(); + break; + + default: + LOG.warn("{} wrap default {}",_session,result); + throw new IOException(result.toString()); + } + + //if (LOG.isDebugEnabled() && result.bytesProduced()>0) + // LOG.debug("{} unwrapped '{}'",_session,buffer); + + return result.bytesConsumed()>0 || result.bytesProduced()>0; + } + + + /* ------------------------------------------------------------ */ + private ByteBuffer extractByteBuffer(Buffer buffer) + { + if (buffer.buffer() instanceof NIOBuffer) + return ((NIOBuffer)buffer.buffer()).getByteBuffer(); + return ByteBuffer.wrap(buffer.array()); + } + + /* ------------------------------------------------------------ */ + public AsyncEndPoint getSslEndPoint() + { + return _sslEndPoint; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s %s", super.toString(), _sslEndPoint); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class SslEndPoint implements AsyncEndPoint + { + public SSLEngine getSslEngine() + { + return _engine; + } + + public AsyncEndPoint getEndpoint() + { + return _aEndp; + } + + public void shutdownOutput() throws IOException + { + synchronized (SslConnection.this) + { + LOG.debug("{} ssl endp.oshut {}",_session,this); + _engine.closeOutbound(); + _oshut=true; + } + flush(); + } + + public boolean isOutputShutdown() + { + synchronized (SslConnection.this) + { + return _oshut||!isOpen()||_engine.isOutboundDone(); + } + } + + public void shutdownInput() throws IOException + { + LOG.debug("{} ssl endp.ishut!",_session); + // We do not do a closeInput here, as SSL does not support half close. + // isInputShutdown works it out itself from buffer state and underlying endpoint state. + } + + public boolean isInputShutdown() + { + synchronized (SslConnection.this) + { + return _endp.isInputShutdown() && + !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) && + !(_inbound!=null&&_inbound.hasContent()); + } + } + + public void close() throws IOException + { + LOG.debug("{} ssl endp.close",_session); + _endp.close(); + } + + public int fill(Buffer buffer) throws IOException + { + int size=buffer.length(); + process(buffer, null); + + int filled=buffer.length()-size; + + if (filled==0 && isInputShutdown()) + return -1; + return filled; + } + + public int flush(Buffer buffer) throws IOException + { + int size = buffer.length(); + process(null, buffer); + return size-buffer.length(); + } + + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + if (header!=null && header.hasContent()) + return flush(header); + if (buffer!=null && buffer.hasContent()) + return flush(buffer); + if (trailer!=null && trailer.hasContent()) + return flush(trailer); + return 0; + } + + public boolean blockReadable(long millisecs) throws IOException + { + long now = System.currentTimeMillis(); + long end=millisecs>0?(now+millisecs):Long.MAX_VALUE; + + while (now - * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an - * SSL connection. - *

    - * There is a named logger "org.eclipse.jetty.http.ssl" - *

    - */ -public class SslSelectChannelEndPoint extends SelectChannelEndPoint -{ - private static final Logger LOG = Log.getLogger(SslSelectChannelEndPoint.class); - - public static final Logger __log=Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl"); - - private static final ByteBuffer[] __NO_BUFFERS={}; - - private final Buffers _buffers; - - private final SSLEngine _engine; - private final SSLSession _session; - private volatile NIOBuffer _inNIOBuffer; - private volatile NIOBuffer _outNIOBuffer; - - private final ByteBuffer[] _gather=new ByteBuffer[2]; - - private boolean _closing=false; - private SSLEngineResult _result; - - private boolean _handshook=false; - private boolean _allowRenegotiate=false; - - private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer - - /* ------------------------------------------------------------ */ - public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine, int maxIdleTime) - throws IOException - { - super(channel,selectSet,key, maxIdleTime); - _buffers=buffers; - - // ssl - _engine=engine; - _session=engine.getSession(); - - if (_debug) __log.debug(_session+" channel="+channel); - } - - /* ------------------------------------------------------------ */ - public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine) - throws IOException - { - super(channel,selectSet,key); - _buffers=buffers; - - // ssl - _engine=engine; - _session=engine.getSession(); - - if (_debug) __log.debug(_session+" channel="+channel); - } - - int _outCount; - - /* ------------------------------------------------------------ */ - private void needOutBuffer() - { - synchronized (this) - { - _outCount++; - if (_outNIOBuffer==null) - _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); - } - } - - /* ------------------------------------------------------------ */ - private void freeOutBuffer() - { - synchronized (this) - { - if (--_outCount<=0 && _outNIOBuffer!=null && _outNIOBuffer.length()==0) - { - _buffers.returnBuffer(_outNIOBuffer); - _outNIOBuffer=null; - _outCount=0; - } - } - } - - int _inCount; - /* ------------------------------------------------------------ */ - private void needInBuffer() - { - synchronized (this) - { - _inCount++; - if(_inNIOBuffer==null) - _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); - } - } - - /* ------------------------------------------------------------ */ - private void freeInBuffer() - { - synchronized (this) - { - if (--_inCount<=0 &&_inNIOBuffer!=null && _inNIOBuffer.length()==0) - { - _buffers.returnBuffer(_inNIOBuffer); - _inNIOBuffer=null; - _inCount=0; - } - } - } - - /* ------------------------------------------------------------ */ - /** - * @return True if SSL re-negotiation is allowed (default false) - */ - public boolean isAllowRenegotiate() - { - return _allowRenegotiate; - } - - /* ------------------------------------------------------------ */ - /** - * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered - * a vulnerability in SSL/TLS with re-negotiation. If your JVM - * does not have CVE-2009-3555 fixed, then re-negotiation should - * not be allowed. - * @param allowRenegotiate true if re-negotiation is allowed (default false) - */ - public void setAllowRenegotiate(boolean allowRenegotiate) - { - _allowRenegotiate = allowRenegotiate; - } - - - /* ------------------------------------------------------------ */ - @Override - public boolean isOutputShutdown() - { - return _engine!=null && _engine.isOutboundDone(); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isInputShutdown() - { - return _engine!=null && _engine.isInboundDone(); - } - - /* ------------------------------------------------------------ */ - @Override - public void shutdownOutput() throws IOException - { - try - { - sslClose(); - } - finally - { - super.shutdownOutput(); - } - } - - /* ------------------------------------------------------------ */ - protected void sslClose() throws IOException - { - if (_closing) - return; - _closing=true; - - // TODO - this really should not be done in a loop here - but with async callbacks. - long end=System.currentTimeMillis()+getMaxIdleTime(); - try - { - while (isOpen() && isBufferingOutput()&& System.currentTimeMillis()0) - flush(); - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(put); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" close wrap "+_result); - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - freeOutBuffer(); - } - - break; - } - } - } - } - catch (Exception x) - { - LOG.debug(x); - super.close(); - } - } - - /* ------------------------------------------------------------ */ - @Override - public void close() throws IOException - { - try - { - sslClose(); - } - finally - { - super.close(); - } - } - - /* ------------------------------------------------------------ */ - /** Fill the buffer with unencrypted bytes. - * Called by a Http Parser when more data is - * needed to continue parsing a request or a response. - */ - @Override - public int fill(Buffer buffer) throws IOException - { - // This end point only works on NIO buffer type (director - // or indirect), so extract the NIO buffer that is wrapped - // by the passed jetty Buffer. - final ByteBuffer bbuf=extractInputBuffer(buffer); - - // remember the original size of the unencrypted buffer - int size=buffer.length(); - - HandshakeStatus initialStatus = _engine.getHandshakeStatus(); - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (bbuf) - { - try - { - // Call the SSLEngine unwrap method to process data in - // the inBuffer. If there is no data in the inBuffer, then - // super.fill is called to read encrypted bytes. - unwrap(bbuf); - - // Loop through the SSL engine state machine - - int wraps=0; - loop: while (true) - { - // If we have encrypted data in output buffer - if (isBufferingOutput()) - { - // we must flush it, as the other end might be - // waiting for that outgoing data before sending - // more incoming data - flush(); - - // If we were unable to flush all the data, then - // we should break the loop and wait for the call - // back to handle when the SelectSet detects that - // the channel is writable again. - if (isBufferingOutput()) - break loop; - } - - // handle the current hand share status - switch(_engine.getHandshakeStatus()) - { - case FINISHED: - case NOT_HANDSHAKING: - // If we are closing, then unwrap must have CLOSED result, - // so return -1 to signal upwards - if (_closing) - return -1; - - // otherwise we break loop with the data we have unwrapped. - break loop; - - case NEED_UNWRAP: - checkRenegotiate(); - // Need more data to be unwrapped so try another call to unwrap - if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) - { - // If the unwrap call did not make any progress and we are still in - // NEED_UNWRAP, then we should break the loop and wait for more data to - // arrive. - break loop; - } - // progress was made so continue the loop. - break; - - case NEED_TASK: - { - // A task needs to be run, so run it! - - Runnable task; - while ((task=_engine.getDelegatedTask())!=null) - { - task.run(); - } - - // Detect SUN JVM Bug!!! - if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && - _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0) - { - // This should be NEED_WRAP - // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. - // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. - // See http://jira.codehaus.org/browse/JETTY-567 for more details - if (_debug) __log.warn(_session+" JETTY-567"); - return -1; - } - break; - } - - case NEED_WRAP: - { - checkRenegotiate(); - // The SSL needs to send some handshake data to the other side, - // so let fill become a flush for a little bit. - wraps++; - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - synchronized(out_buffer) - { - try - { - // call wrap with empty application buffers, so it can - // generate required handshake messages into _outNIOBuffer - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" fill wrap "+_result); - switch(_result.getStatus()) - { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - LOG.warn("wrap {}",_result); - case CLOSED: - _closing=true; - } - - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - } - } - - // flush the encrypted outNIOBuffer - flush(); - freeOutBuffer(); - - break; - } - } - } - } - finally - { - // reset the Buffers - buffer.setPutIndex(bbuf.position()); - bbuf.position(0); - } - - // return the number of unencrypted bytes filled. - int filled=buffer.length()-size; - if (filled>0) - _handshook=true; - return filled; - } - } - - /* ------------------------------------------------------------ */ - @Override - public int flush(Buffer buffer) throws IOException - { - return flush(buffer,null,null); - } - - - /* ------------------------------------------------------------ */ - /* - */ - @Override - public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException - { - int consumed=0; - int available=header.length(); - if (buffer!=null) - available+=buffer.length(); - - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - loop: while (true) - { - if (_outNIOBuffer.length()>0) - { - flush(); - if (isBufferingOutput()) - break loop; - } - - switch(_engine.getHandshakeStatus()) - { - case FINISHED: - case NOT_HANDSHAKING: - if (_closing || available==0) - { - if (consumed==0) - consumed= -1; - break loop; - } - - int c; - if (header!=null && header.length()>0) - { - if (buffer!=null && buffer.length()>0) - c=wrap(header,buffer); - else - c=wrap(header); - } - else - c=wrap(buffer); - - - if (c>0) - { - _handshook=true; - consumed+=c; - available-=c; - } - else if (c<0) - { - if (consumed==0) - consumed=-1; - break loop; - } - - break; - - case NEED_UNWRAP: - checkRenegotiate(); - Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); - try - { - ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer(); - if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) - { - break loop; - } - } - finally - { - _buffers.returnBuffer(buf); - } - - break; - - case NEED_TASK: - { - Runnable task; - while ((task=_engine.getDelegatedTask())!=null) - { - task.run(); - } - break; - } - - case NEED_WRAP: - { - checkRenegotiate(); - synchronized(out_buffer) - { - try - { - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" flush wrap "+_result); - switch(_result.getStatus()) - { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - LOG.warn("unwrap {}",_result); - case CLOSED: - _closing=true; - } - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - } - } - - flush(); - if (isBufferingOutput()) - break loop; - - break; - } - } - } - - freeOutBuffer(); - return consumed; - } - - /* ------------------------------------------------------------ */ - @Override - public void flush() throws IOException - { - if (_outNIOBuffer==null) - return; - - int len=_outNIOBuffer.length(); - if (isBufferingOutput()) - { - int flushed=super.flush(_outNIOBuffer); - if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len); - if (isBufferingOutput()) - { - // Try again after yield.... cheaper than a reschedule. - Thread.yield(); - flushed=super.flush(_outNIOBuffer); - if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len); - } - else if (_closing && !_engine.isOutboundDone()) - { - _engine.closeOutbound(); - } - } - } - - /* ------------------------------------------------------------ */ - private void checkRenegotiate() throws IOException - { - if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen()) - { - LOG.warn("SSL renegotiate denied: "+_channel); - super.close(); - } - } - - /* ------------------------------------------------------------ */ - private ByteBuffer extractInputBuffer(Buffer buffer) - { - assert buffer instanceof NIOBuffer; - NIOBuffer nbuf=(NIOBuffer)buffer; - ByteBuffer bbuf=nbuf.getByteBuffer(); - bbuf.position(buffer.putIndex()); - return bbuf; - } - - /* ------------------------------------------------------------ */ - /** - * @return true if progress is made - */ - private boolean unwrap(ByteBuffer buffer) throws IOException - { - needInBuffer(); - ByteBuffer in_buffer=_inNIOBuffer.getByteBuffer(); - - if (_inNIOBuffer.hasContent()) - _inNIOBuffer.compact(); - else - _inNIOBuffer.clear(); - - int total_filled=0; - boolean remoteClosed = false; - // loop filling as much encrypted data as we can into the buffer - while (_inNIOBuffer.space()>0 && super.isOpen()) - { - try - { - int filled=super.fill(_inNIOBuffer); - if (_debug) __log.debug(_session+" unwrap filled "+filled); - if (filled < 0) - remoteClosed = true; - // break the loop if no progress is made (we have read everything there is to read) - if (filled<=0) - break; - total_filled+=filled; - } - catch(IOException e) - { - if (_inNIOBuffer.length()==0) - { - freeInBuffer(); - if (_outNIOBuffer!=null) - { - _outNIOBuffer.clear(); - freeOutBuffer(); - } - throw e; - } - break; - } - } - - // If we have no progress and no data - if (total_filled==0 && _inNIOBuffer.length()==0) - { - if (isOpen() && remoteClosed) - { - try - { - _engine.closeInbound(); - } - catch (SSLException x) - { - // It may happen, for example, in case of truncation - // attacks, we close so that we do not spin forever - super.close(); - } - } - - freeInBuffer(); - freeOutBuffer(); - - if (!isOpen()) - throw new EofException(); - - return false; - } - - // We have some in data, so try to unwrap it. - try - { - // inBuffer is the NIO buffer inside the _inNIOBuffer, - // so update its position and limit from the inNIOBuffer. - in_buffer.position(_inNIOBuffer.getIndex()); - in_buffer.limit(_inNIOBuffer.putIndex()); - - // Do the unwrap - _result=_engine.unwrap(in_buffer,buffer); - if (_debug) __log.debug(_session+" unwrap unwrap "+_result); - - // skip the bytes consumed - _inNIOBuffer.skip(_result.bytesConsumed()); - } - catch(SSLException e) - { - LOG.warn(getRemoteAddr() + ":" + getRemotePort() + " " + e); - freeOutBuffer(); - super.close(); - throw e; - } - finally - { - // reset the buffer so it can be managed by the _inNIOBuffer again. - in_buffer.position(0); - in_buffer.limit(in_buffer.capacity()); - freeInBuffer(); - } - - // handle the unwrap results - switch(_result.getStatus()) - { - case BUFFER_OVERFLOW: - throw new IllegalStateException(_result.toString()+" "+buffer.position()+" "+buffer.limit()); - - case BUFFER_UNDERFLOW: - // Not enough data, - // If we are closed, we will never get more, so EOF - // else return and we will be tried again - // later when more data arriving causes another dispatch. - if (LOG.isDebugEnabled()) LOG.debug("unwrap {}",_result); - if(!isOpen()) - { - _inNIOBuffer.clear(); - if (_outNIOBuffer!=null) - _outNIOBuffer.clear(); - throw new EofException(); - } - return (total_filled > 0); - - case CLOSED: - _closing=true; - // return true is some bytes somewhere were moved about. - return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; - case OK: - // return true is some bytes somewhere were moved about. - return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; - default: - LOG.warn("unwrap "+_result); - throw new IOException(_result.toString()); - } - } - - - /* ------------------------------------------------------------ */ - private ByteBuffer extractOutputBuffer(Buffer buffer) - { - if (buffer.buffer() instanceof NIOBuffer) - return ((NIOBuffer)buffer.buffer()).getByteBuffer(); - - return ByteBuffer.wrap(buffer.array()); - } - - /* ------------------------------------------------------------ */ - private int wrap(final Buffer header, final Buffer buffer) throws IOException - { - _gather[0]=extractOutputBuffer(header); - - synchronized(_gather[0]) - { - _gather[0].position(header.getIndex()); - _gather[0].limit(header.putIndex()); - - _gather[1]=extractOutputBuffer(buffer); - - synchronized(_gather[1]) - { - _gather[1].position(buffer.getIndex()); - _gather[1].limit(buffer.putIndex()); - - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - synchronized(out_buffer) - { - int consumed=0; - try - { - _outNIOBuffer.clear(); - out_buffer.position(0); - out_buffer.limit(out_buffer.capacity()); - - _result=null; - _result=_engine.wrap(_gather,out_buffer); - if (_debug) __log.debug(_session+" wrap wrap "+_result); - _outNIOBuffer.setGetIndex(0); - _outNIOBuffer.setPutIndex(_result.bytesProduced()); - consumed=_result.bytesConsumed(); - } - catch(SSLException e) - { - LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e); - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - - if (consumed>0) - { - int len=consumed0) - { - int len=consumed0?_result.bytesConsumed():-1; - - default: - LOG.warn("wrap "+_result); - throw new IOException(_result.toString()); - } - } - - /* ------------------------------------------------------------ */ - private int wrap(final Buffer buffer) throws IOException - { - _gather[0]=extractOutputBuffer(buffer); - synchronized(_gather[0]) - { - ByteBuffer bb; - - _gather[0].position(buffer.getIndex()); - _gather[0].limit(buffer.putIndex()); - - int consumed=0; - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - synchronized(out_buffer) - { - try - { - _outNIOBuffer.clear(); - out_buffer.position(0); - out_buffer.limit(out_buffer.capacity()); - _result=null; - _result=_engine.wrap(_gather[0],out_buffer); - if (_debug) __log.debug(_session+" wrap wrap "+_result); - _outNIOBuffer.setGetIndex(0); - _outNIOBuffer.setPutIndex(_result.bytesProduced()); - consumed=_result.bytesConsumed(); - } - catch(SSLException e) - { - LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e); - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - - if (consumed>0) - { - int len=consumed0?_result.bytesConsumed():-1; - - default: - LOG.warn("wrap "+_result); - throw new IOException(_result.toString()); - } - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isBufferingInput() - { - final Buffer in = _inNIOBuffer; - return in==null?false:_inNIOBuffer.hasContent(); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isBufferingOutput() - { - final NIOBuffer b=_outNIOBuffer; - return b==null?false:b.hasContent(); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isBufferred() - { - return true; - } - - /* ------------------------------------------------------------ */ - public SSLEngine getSSLEngine() - { - return _engine; - } - - /* ------------------------------------------------------------ */ - @Override - public void scheduleWrite() - { - // only set !writable if we are not waiting for input - if (!HandshakeStatus.NEED_UNWRAP.equals(_engine.getHandshakeStatus()) || super.isBufferingOutput()) - super.scheduleWrite(); - } - - /* ------------------------------------------------------------ */ - @Override - public String toString() - { - final NIOBuffer i=_inNIOBuffer; - final NIOBuffer o=_outNIOBuffer; - return "SSL"+super.toString()+","+_engine.getHandshakeStatus()+", in/out="+ - (i==null?0:_inNIOBuffer.length())+"/"+(o==null?0:o.length())+ - " bi/o="+isBufferingInput()+"/"+isBufferingOutput()+ - " "+_result; - } -} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java index 75922f50437..51be3cd751f 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java @@ -1,62 +1,157 @@ package org.eclipse.jetty.io; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.concurrent.Exchanger; -import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import org.eclipse.jetty.io.bio.SocketEndPoint; import org.eclipse.jetty.io.nio.IndirectNIOBuffer; -import org.junit.Assert; import org.junit.Test; -public class EndPointTest +public abstract class EndPointTest { + public static class EndPointPair + { + public T client; + public T server; + } + + protected abstract EndPointPair newConnection() throws Exception; + + @Test - public void testSocketEndPoints() throws Exception + public void testClientServerExchange() throws Exception { - final ServerSocket server = new ServerSocket(); - server.bind(null); + EndPointPair c = newConnection(); + Buffer buffer = new IndirectNIOBuffer(4096); - final Exchanger accepted = new Exchanger(); - new Thread(){ - public void run() - { - try - { - accepted.exchange(server.accept()); - } - catch(Exception e) - { - e.printStackTrace(); - } - } - }.start(); + // Client sends a request + c.client.flush(new ByteArrayBuffer("request")); - Socket s0 = new Socket(server.getInetAddress(),server.getLocalPort()); - Socket s1 = accepted.exchange(null,5,TimeUnit.SECONDS); + // Server receives the request + int len = c.server.fill(buffer); + assertEquals(7,len); + assertEquals("request",buffer.toString()); + + // Client and server are open + assertTrue(c.client.isOpen()); + assertFalse(c.client.isInputShutdown()); + assertFalse(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertFalse(c.server.isOutputShutdown()); - SocketEndPoint in = new SocketEndPoint(s0); - SocketEndPoint out = new SocketEndPoint(s1); + // Server sends response and closes output + c.server.flush(new ByteArrayBuffer("response")); + c.server.shutdownOutput(); + + // client server are open, server is oshut + assertTrue(c.client.isOpen()); + assertFalse(c.client.isInputShutdown()); + assertFalse(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); + + // Client reads response + buffer.clear(); + len = c.client.fill(buffer); + assertEquals(8,len); + assertEquals("response",buffer.toString()); + + // Client and server are open, server is oshut + assertTrue(c.client.isOpen()); + assertFalse(c.client.isInputShutdown()); + assertFalse(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); + + // Client reads -1 + buffer.clear(); + len = c.client.fill(buffer); + assertEquals(-1,len); + + // Client and server are open, server is oshut, client is ishut + assertTrue(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertFalse(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); + + // Client shutsdown output, which is a close because already ishut + c.client.shutdownOutput(); + + // Client is closed. Server is open and oshut + assertFalse(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertTrue(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); + + // Server reads close + buffer.clear(); + len = c.server.fill(buffer); + assertEquals(-1,len); + + // Client and Server are closed + assertFalse(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertTrue(c.client.isOutputShutdown()); + assertFalse(c.server.isOpen()); + assertTrue(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); - check(in,out); } - - private void check(EndPoint in, EndPoint out) throws Exception + + + @Test + public void testClientClose() throws Exception { - String data="Now is the time for all good men to come to the aid of the party"; - Buffer send = new ByteArrayBuffer(data); - Buffer receive = new IndirectNIOBuffer(4096); + EndPointPair c = newConnection(); + Buffer buffer = new IndirectNIOBuffer(4096); - int lo=out.flush(send); - int li=in.fill(receive); + c.client.flush(new ByteArrayBuffer("request")); + int len = c.server.fill(buffer); + assertEquals(7,len); + assertEquals("request",buffer.toString()); + + assertTrue(c.client.isOpen()); + assertFalse(c.client.isInputShutdown()); + assertFalse(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertFalse(c.server.isOutputShutdown()); - Assert.assertEquals(data.length(),lo); - Assert.assertEquals(data.length(),li); - Assert.assertEquals(data,receive.toString()); + c.client.close(); + + assertFalse(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertTrue(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertFalse(c.server.isInputShutdown()); + assertFalse(c.server.isOutputShutdown()); - in.close(); - out.close(); - } + len = c.server.fill(buffer); + assertEquals(-1,len); + + assertFalse(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertTrue(c.client.isOutputShutdown()); + assertTrue(c.server.isOpen()); + assertTrue(c.server.isInputShutdown()); + assertFalse(c.server.isOutputShutdown()); + + c.server.shutdownOutput(); + + assertFalse(c.client.isOpen()); + assertTrue(c.client.isInputShutdown()); + assertTrue(c.client.isOutputShutdown()); + assertFalse(c.server.isOpen()); + assertTrue(c.server.isInputShutdown()); + assertTrue(c.server.isOutputShutdown()); + } + } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java index d2759a6f84a..56400165ab9 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java @@ -14,10 +14,22 @@ package org.eclipse.jetty.io; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import junit.framework.Assert; + +import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.IO; import org.junit.Test; @@ -42,33 +54,271 @@ public class IOTest out.toString(), "The quick brown fox jumped over the lazy dog"); } + + @Test + public void testHalfClose() throws Exception + { + ServerSocket connector = new ServerSocket(0); + + Socket client = new Socket("localhost",connector.getLocalPort()); + Socket server = connector.accept(); + + // we can write both ways + client.getOutputStream().write(1); + assertEquals(1,server.getInputStream().read()); + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + // shutdown output results in read -1 + client.shutdownOutput(); + assertEquals(-1,server.getInputStream().read()); + + // Even though EOF has been read, the server input is not seen as shutdown + assertFalse(server.isInputShutdown()); + + // and we can read -1 again + assertEquals(-1,server.getInputStream().read()); + + // but cannot write + try { client.getOutputStream().write(1); fail("exception expected"); } catch (SocketException e) {} + + // but can still write in opposite direction. + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + + // server can shutdown input to match the shutdown out of client + server.shutdownInput(); + + // now we EOF instead of reading -1 + try { server.getInputStream().read(); fail("exception expected"); } catch (SocketException e) {} + + + // but can still write in opposite direction. + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + // client can shutdown input + client.shutdownInput(); + + // now we EOF instead of reading -1 + try { client.getInputStream().read(); fail("exception expected"); } catch (SocketException e) {} + + // But we can still write at the server (data which will never be read) + server.getOutputStream().write(1); + + // and the server output is not shutdown + assertFalse( server.isOutputShutdown() ); + + // until we explictly shut it down + server.shutdownOutput(); + + // and now we can't write + try { server.getOutputStream().write(1); fail("exception expected"); } catch (SocketException e) {} + + // but the sockets are still open + assertFalse(client.isClosed()); + assertFalse(server.isClosed()); + + // but if we close one end + client.close(); + + // it is seen as closed. + assertTrue(client.isClosed()); + + // but not the other end + assertFalse(server.isClosed()); + + // which has to be closed explictly + server.close(); + assertTrue(server.isClosed()); + + } + + + @Test + public void testHalfCloseClientServer() throws Exception + { + ServerSocketChannel connector = ServerSocketChannel.open(); + connector.socket().bind(null); + + Socket client = SocketChannel.open(connector.socket().getLocalSocketAddress()).socket(); + client.setSoTimeout(1000); + client.setSoLinger(false,-1); + Socket server = connector.accept().socket(); + server.setSoTimeout(1000); + server.setSoLinger(false,-1); + + // Write from client to server + client.getOutputStream().write(1); + + // Server reads + assertEquals(1,server.getInputStream().read()); + + // Write from server to client with oshut + server.getOutputStream().write(1); + System.err.println("OSHUT "+server); + server.shutdownOutput(); + + // Client reads response + assertEquals(1,client.getInputStream().read()); + + try + { + // Client reads -1 and does ishut + assertEquals(-1,client.getInputStream().read()); + assertFalse(client.isInputShutdown()); + System.err.println("ISHUT "+client); + client.shutdownInput(); + + // Client ??? + System.err.println("OSHUT "+client); + client.shutdownOutput(); + System.err.println("CLOSE "+client); + client.close(); + + // Server reads -1, does ishut and then close + assertEquals(-1,server.getInputStream().read()); + assertFalse(server.isInputShutdown()); + System.err.println("ISHUT "+server); + + try + { + server.shutdownInput(); + } + catch(SocketException e) + { + System.err.println(e); + } + System.err.println("CLOSE "+server); + server.close(); + + } + catch(Exception e) + { + System.err.println(e); + assertTrue(OS.IS_OSX); + } + } @Test - public void testStringSpeed() + public void testHalfCloseBadClient() throws Exception { - String s="012345678901234567890000000000000000000000000"; - char[] ca = new char[s.length()]; - int loops=1000000; + ServerSocketChannel connector = ServerSocketChannel.open(); + connector.socket().bind(null); + + Socket client = SocketChannel.open(connector.socket().getLocalSocketAddress()).socket(); + client.setSoTimeout(1000); + client.setSoLinger(false,-1); + Socket server = connector.accept().socket(); + server.setSoTimeout(1000); + server.setSoLinger(false,-1); + + // Write from client to server + client.getOutputStream().write(1); + + // Server reads + assertEquals(1,server.getInputStream().read()); - long start=System.currentTimeMillis(); - long result=0; - for (int loop=0;loop0;) - result+=s.charAt(c); - } - long end=System.currentTimeMillis(); - System.err.println("charAt "+(end-start)+" "+result); + // Write from server to client with oshut + server.getOutputStream().write(1); + System.err.println("OSHUT "+server); + server.shutdownOutput(); - start=System.currentTimeMillis(); - result=0; - for (int loop=0;loop0;) - result+=ca[c]; + // Client reads response + assertEquals(1,client.getInputStream().read()); + + // Client reads -1 + assertEquals(-1,client.getInputStream().read()); + assertFalse(client.isInputShutdown()); + + // Client can still write as we are half closed + client.getOutputStream().write(1); + + // Server can still read + assertEquals(1,server.getInputStream().read()); + + // Server now closes + server.close(); + + // Client still reads -1 (not broken pipe !!) + assertEquals(-1,client.getInputStream().read()); + assertFalse(client.isInputShutdown()); + + Thread.sleep(100); + + // Client still reads -1 (not broken pipe !!) + assertEquals(-1,client.getInputStream().read()); + assertFalse(client.isInputShutdown()); + + // Client can still write data even though server is closed??? + client.getOutputStream().write(1); + Thread.sleep(100); + client.getOutputStream().write(1); + + // Client eventually sees Broken Pipe + int i=0; + try + { + for (i=0;i<100000;i++) + client.getOutputStream().write(1); + + Assert.fail(); + } + catch (IOException e) + { + } + client.close(); + + } + catch (Exception e) + { + System.err.println("PLEASE INVESTIGATE:"); + e.printStackTrace(); } - end=System.currentTimeMillis(); - System.err.println("getChars "+(end-start)+" "+result); } + + @Test + public void testReset() throws Exception + { + ServerSocket connector; + Socket client; + Socket server; + + connector = new ServerSocket(9123); + client = new Socket("127.0.0.1",connector.getLocalPort()); + server = connector.accept(); + client.setTcpNoDelay(true); + client.setSoLinger(true,0); + server.setTcpNoDelay(true); + server.setSoLinger(true,0); + + client.getOutputStream().write(1); + assertEquals(1,server.getInputStream().read()); + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + // Server generator shutdowns output after non persistent sending response. + server.shutdownOutput(); + + // client endpoint reads EOF and shutdown input as result + assertEquals(-1,client.getInputStream().read()); + client.shutdownInput(); + + // client connection see's EOF and shutsdown output as no more requests to be sent. + client.shutdownOutput(); + + // Since input already shutdown, client also closes socket. + client.close(); + + // Server reads the EOF from client oshut and shut's down it's input + assertEquals(-1,server.getInputStream().read()); + server.shutdownInput(); + + // Since output was already shutdown, server closes + server.close(); + } + } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java new file mode 100644 index 00000000000..277c6c7ccba --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java @@ -0,0 +1,38 @@ +package org.eclipse.jetty.io.bio; + +import java.net.ServerSocket; +import java.net.Socket; + +import org.eclipse.jetty.io.EndPointTest; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class SocketEndPointTest extends EndPointTest +{ + static ServerSocket connector; + + @BeforeClass + public static void open() throws Exception + { + connector = new ServerSocket(); + connector.bind(null); + } + + @AfterClass + public static void close() throws Exception + { + connector.close(); + connector=null; + } + + @Override + protected EndPointPair newConnection() throws Exception + { + EndPointPair c = new EndPointPair(); + c.client=new SocketEndPoint(new Socket(connector.getInetAddress(),connector.getLocalPort())); + c.server=new SocketEndPoint(connector.accept()); + return c; + } + + +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java new file mode 100644 index 00000000000..1d8d5b2c7eb --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java @@ -0,0 +1,43 @@ +package org.eclipse.jetty.io.nio; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.EndPointTest; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class ChannelEndPointTest extends EndPointTest +{ + static ServerSocketChannel connector; + + @BeforeClass + public static void open() throws Exception + { + connector = ServerSocketChannel.open(); + connector.socket().bind(null); + } + + @AfterClass + public static void close() throws Exception + { + connector.close(); + connector=null; + } + + @Override + protected EndPointPair newConnection() throws Exception + { + EndPointPair c = new EndPointPair(); + + c.client=new ChannelEndPoint(SocketChannel.open(connector.socket().getLocalSocketAddress())); + c.server=new ChannelEndPoint(connector.accept()); + return c; + } + + @Override + public void testClientServerExchange() throws Exception + { + super.testClientServerExchange(); + } +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java new file mode 100644 index 00000000000..9366d8ce843 --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java @@ -0,0 +1,131 @@ +// ======================================================================== +// Copyright (c) 2004-2009 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.io.nio; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +import org.junit.Test; + +/** + * + */ +public class NIOTest +{ + @Test + public void testSelector() throws Exception + { + ServerSocket acceptor = new ServerSocket(0); + + Selector selector = Selector.open(); + + // Create client server socket pair + SocketChannel client = SocketChannel.open(acceptor.getLocalSocketAddress()); + Socket server = acceptor.accept(); + server.setTcpNoDelay(true); + + // Make the client non blocking and register it with selector for reads + client.configureBlocking(false); + SelectionKey key = client.register(selector,SelectionKey.OP_READ); + + // assert it is not selected + assertTrue(key.isValid()); + assertFalse(key.isReadable()); + assertEquals(0,key.readyOps()); + + // try selecting and assert nothing selected + int selected = selector.selectNow(); + assertEquals(0,selected); + assertEquals(0,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertFalse(key.isReadable()); + assertEquals(0,key.readyOps()); + + // Write a byte from server to client + server.getOutputStream().write(42); + server.getOutputStream().flush(); + + // select again and assert selection found for read + selected = selector.select(1000); + assertEquals(1,selected); + assertEquals(1,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // select again and see that it is not reselect, but stays selected + selected = selector.select(100); + assertEquals(0,selected); + assertEquals(1,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // read the byte + ByteBuffer buf = ByteBuffer.allocate(1024); + int len=client.read(buf); + assertEquals(1,len); + buf.flip(); + assertEquals(42,buf.get()); + buf.clear(); + + // But this does not change the key + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // Even if we select again ? + selected = selector.select(100); + assertEquals(0,selected); + assertEquals(1,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // Unless we remove the key from the select set + // and then it is still flagged as isReadable() + selector.selectedKeys().clear(); + assertEquals(0,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // Now if we select again - it is still flagged as readable!!! + selected = selector.select(100); + assertEquals(0,selected); + assertEquals(0,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isReadable()); + assertEquals(1,key.readyOps()); + + // Only when it is selected for something else does that state change. + key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); + selected = selector.select(1000); + assertEquals(1,selected); + assertEquals(1,selector.selectedKeys().size()); + assertTrue(key.isValid()); + assertTrue(key.isWritable()); + assertFalse(key.isReadable()); + assertEquals(SelectionKey.OP_WRITE,key.readyOps()); + } + +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java new file mode 100644 index 00000000000..d6a6a46a06a --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java @@ -0,0 +1,187 @@ +package org.eclipse.jetty.io.nio; + +import java.io.File; +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +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.SSLSocket; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + + +public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest +{ + static SslContextFactory __sslCtxFactory=new SslContextFactory(); + + @BeforeClass + public static void initSslEngine() throws Exception + { + File keystore = MavenTestingUtils.getTestResourceFile("keystore"); + __sslCtxFactory.setKeyStorePath(keystore.getAbsolutePath()); + __sslCtxFactory.setKeyStorePassword("storepwd"); + __sslCtxFactory.setKeyManagerPassword("keypwd"); + __sslCtxFactory.start(); + } + + @Override + protected Socket newClient() throws IOException + { + SSLSocket socket = __sslCtxFactory.newSslSocket(); + socket.connect(_connector.socket().getLocalSocketAddress()); + return socket; + } + + @Override + protected AsyncConnection newConnection(SocketChannel channel, EndPoint endpoint) + { + SSLEngine engine = __sslCtxFactory.newSslEngine(); + engine.setUseClientMode(false); + SslConnection connection = new SslConnection(engine,endpoint); + + AsyncConnection delegate = super.newConnection(channel,connection.getSslEndPoint()); + connection.getSslEndPoint().setConnection(delegate); + return connection; + } + + @Test + @Override + public void testEcho() throws Exception + { + super.testEcho(); + } + + + @Test + @Override + public void testShutdown() throws Exception + { + // SSL does not do half closes + } + + @Test + public void testTcpClose() throws Exception + { + + // This test replaces SSLSocket() with a very manual SSL client + // so we can close TCP underneath SSL. + + SocketChannel client = SocketChannel.open(_connector.socket().getLocalSocketAddress()); + client.socket().setSoTimeout(500); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + _manager.register(server); + + SSLEngine engine = __sslCtxFactory.newSslEngine(); + engine.setUseClientMode(true); + engine.beginHandshake(); + + ByteBuffer appOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); + ByteBuffer sslOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2); + ByteBuffer appIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); + ByteBuffer sslIn = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2); + + boolean debug=SslConnection.LOG.isDebugEnabled(); + + if (debug) System.err.println(engine.getHandshakeStatus()); + int loop=20; + while (engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING) + { + if (--loop==0) + throw new IllegalStateException(); + + if (engine.getHandshakeStatus()==HandshakeStatus.NEED_WRAP) + { + if (debug) System.err.printf("sslOut %d-%d-%d%n",sslOut.position(),sslOut.limit(),sslOut.capacity()); + if (debug) System.err.printf("appOut %d-%d-%d%n",appOut.position(),appOut.limit(),appOut.capacity()); + SSLEngineResult result =engine.wrap(appOut,sslOut); + if (debug) System.err.println(result); + sslOut.flip(); + int flushed=client.write(sslOut); + if (debug) System.err.println("out="+flushed); + sslOut.clear(); + } + + if (engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity()); + if (sslIn.position()==0) + { + int filled=client.read(sslIn); + if (debug) System.err.println("in="+filled); + } + sslIn.flip(); + if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity()); + SSLEngineResult result =engine.unwrap(sslIn,appIn); + if (debug) System.err.println(result); + if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity()); + if (sslIn.hasRemaining()) + sslIn.compact(); + else + sslIn.clear(); + if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity()); + } + + if (engine.getHandshakeStatus()==HandshakeStatus.NEED_TASK) + { + Runnable task; + while ((task=engine.getDelegatedTask())!=null) + task.run(); + if (debug) System.err.println(engine.getHandshakeStatus()); + } + } + + if (debug) System.err.println("\nSay Hello"); + + // write a message + appOut.put("HelloWorld".getBytes("UTF-8")); + appOut.flip(); + SSLEngineResult result =engine.wrap(appOut,sslOut); + if (debug) System.err.println(result); + sslOut.flip(); + int flushed=client.write(sslOut); + if (debug) System.err.println("out="+flushed); + sslOut.clear(); + appOut.clear(); + + // read the response + int filled=client.read(sslIn); + if (debug) System.err.println("in="+filled); + sslIn.flip(); + result =engine.unwrap(sslIn,appIn); + if (debug) System.err.println(result); + if (sslIn.hasRemaining()) + sslIn.compact(); + else + sslIn.clear(); + + appIn.flip(); + String reply= new String(appIn.array(),appIn.arrayOffset(),appIn.remaining()); + appIn.clear(); + + Assert.assertEquals("HelloWorld",reply); + + SelectorManager.LOG.info("javax.net.ssl.SSLException: Inbound closed... is expected soon"); + if (debug) System.err.println("\nSudden Death"); + client.socket().shutdownOutput(); + + filled=client.read(sslIn); + Assert.assertEquals(-1,filled); + } + + @Test + public void testStress() throws Exception + { + super.testStress(); + } +} diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java new file mode 100644 index 00000000000..768f872258e --- /dev/null +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java @@ -0,0 +1,434 @@ +package org.eclipse.jetty.io.nio; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SelectChannelEndPointTest +{ + protected SelectChannelEndPoint _lastEndp; + protected ServerSocketChannel _connector; + protected QueuedThreadPool _threadPool = new QueuedThreadPool(); + protected SelectorManager _manager = new SelectorManager() + { + @Override + public boolean dispatch(Runnable task) + { + return _threadPool.dispatch(task); + } + + @Override + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + } + + @Override + protected void endPointOpened(SelectChannelEndPoint endpoint) + { + } + + @Override + protected void endPointUpgraded(ConnectedEndPoint endpoint, org.eclipse.jetty.io.Connection oldConnection) + { + } + + @Override + public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) + { + return SelectChannelEndPointTest.this.newConnection(channel,endpoint); + } + + @Override + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet,key,2000); + endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); + _lastEndp=endp; + return endp; + } + }; + + // Must be volatile or the test may fail spuriously + private volatile int _blockAt=0; + + @Before + public void startManager() throws Exception + { + _connector = ServerSocketChannel.open(); + _connector.socket().bind(null); + _threadPool.start(); + _manager.start(); + } + + @After + public void stopManager() throws Exception + { + _manager.stop(); + _threadPool.stop(); + _connector.close(); + } + + protected Socket newClient() throws IOException + { + return new Socket(_connector.socket().getInetAddress(),_connector.socket().getLocalPort()); + } + + protected AsyncConnection newConnection(SocketChannel channel, EndPoint endpoint) + { + return new TestConnection(endpoint); + } + + public class TestConnection extends AbstractConnection implements AsyncConnection + { + NIOBuffer _in = new IndirectNIOBuffer(32*1024); + NIOBuffer _out = new IndirectNIOBuffer(32*1024); + + public TestConnection(EndPoint endp) + { + super(endp); + } + + public org.eclipse.jetty.io.Connection handle() throws IOException + { + boolean progress=true; + while(progress) + { + progress=false; + _in.compact(); + if (_in.space()>0 && _endp.fill(_in)>0) + progress=true; + + while (_blockAt>0 && _in.length()>0 && _in.length()<_blockAt) + { + _endp.blockReadable(10000); + if (_in.space()>0 && _endp.fill(_in)>0) + progress=true; + } + + if (_in.hasContent() && _in.skip(_out.put(_in))>0) + progress=true; + + if (_out.hasContent() && _endp.flush(_out)>0) + progress=true; + + _out.compact(); + + if (!_out.hasContent() && _endp.isInputShutdown()) + _endp.shutdownOutput(); + } + return this; + } + + public boolean isIdle() + { + return false; + } + + public boolean isSuspended() + { + return false; + } + + public void onClose() + { + // System.err.println("onClose"); + } + + public void onInputShutdown() throws IOException + { + // System.err.println("onInputShutdown"); + } + + } + + @Test + public void testEcho() throws Exception + { + Socket client = newClient(); + + client.setSoTimeout(500); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + + _manager.register(server); + + // Write client to server + client.getOutputStream().write("HelloWorld".getBytes("UTF-8")); + + // Verify echo server to client + for (char c : "HelloWorld".toCharArray()) + { + int b = client.getInputStream().read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + + // wait for read timeout + long start=System.currentTimeMillis(); + try + { + client.getInputStream().read(); + Assert.fail(); + } + catch(SocketTimeoutException e) + { + assertTrue(System.currentTimeMillis()-start>=400); + } + + // write then shutdown + client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8")); + + // Verify echo server to client + for (char c : "Goodbye Cruel TLS".toCharArray()) + { + int b = client.getInputStream().read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + client.close(); + + int i=0; + while (server.isOpen()) + { + assert(i++<10); + Thread.sleep(10); + } + + } + + + @Test + public void testShutdown() throws Exception + { + Socket client = newClient(); + + client.setSoTimeout(500); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + + _manager.register(server); + + // Write client to server + client.getOutputStream().write("HelloWorld".getBytes("UTF-8")); + + // Verify echo server to client + for (char c : "HelloWorld".toCharArray()) + { + int b = client.getInputStream().read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + + // wait for read timeout + long start=System.currentTimeMillis(); + try + { + client.getInputStream().read(); + Assert.fail(); + } + catch(SocketTimeoutException e) + { + assertTrue(System.currentTimeMillis()-start>=400); + } + + // write then shutdown + client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8")); + client.shutdownOutput(); + + + // Verify echo server to client + for (char c : "Goodbye Cruel TLS".toCharArray()) + { + int b = client.getInputStream().read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + + // Read close + assertEquals(-1,client.getInputStream().read()); + + } + + + + @Test + public void testBlockIn() throws Exception + { + Socket client = newClient(); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + + _manager.register(server); + + OutputStream clientOutputStream = client.getOutputStream(); + InputStream clientInputStream = client.getInputStream(); + + int specifiedTimeout = 400; + client.setSoTimeout(specifiedTimeout); + + // Write 8 and cause block for 10 + _blockAt=10; + clientOutputStream.write("12345678".getBytes("UTF-8")); + clientOutputStream.flush(); + + Thread.sleep(2 * specifiedTimeout); + + // No echo as blocking for 10 + long start=System.currentTimeMillis(); + try + { + int b = clientInputStream.read(); + Assert.fail("Should have timed out waiting for a response, but read "+b); + } + catch(SocketTimeoutException e) + { + int elapsed = Long.valueOf(System.currentTimeMillis() - start).intValue(); + System.err.println("blocked for " + elapsed+ "ms"); + Assert.assertThat("Expected timeout", elapsed, greaterThanOrEqualTo(3*specifiedTimeout/4)); + } + + // write remaining characters + clientOutputStream.write("90ABCDEF".getBytes("UTF-8")); + clientOutputStream.flush(); + + // Verify echo server to client + for (char c : "1234567890ABCDEF".toCharArray()) + { + int b = clientInputStream.read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + } + + @Test + public void testIdle() throws Exception + { + Socket client = newClient(); + + client.setSoTimeout(3000); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + + _manager.register(server); + + // Write client to server + client.getOutputStream().write("HelloWorld".getBytes("UTF-8")); + + // Verify echo server to client + for (char c : "HelloWorld".toCharArray()) + { + int b = client.getInputStream().read(); + assertTrue(b>0); + assertEquals(c,(char)b); + } + + // Set Max idle + _lastEndp.setMaxIdleTime(500); + + // read until idle shutdown received + long start=System.currentTimeMillis(); + int b=client.getInputStream().read(); + assertEquals(-1,b); + long idle=System.currentTimeMillis()-start; + assertTrue(idle>400); + assertTrue(idle<2000); + + // But endpoint is still open. + assertTrue(_lastEndp.isOpen()); + + + // Wait for another idle callback + Thread.sleep(2000); + // endpoint is closed. + + assertFalse(_lastEndp.isOpen()); + + } + + + + @Test + public void testStress() throws Exception + { + Socket client = newClient(); + client.setSoTimeout(30000); + + SocketChannel server = _connector.accept(); + server.configureBlocking(false); + + _manager.register(server); + int writes = 100000; + + final byte[] bytes="HelloWorld".getBytes("UTF-8"); + final CountDownLatch latch = new CountDownLatch(writes); + final InputStream in = new BufferedInputStream(client.getInputStream()); + final long start = System.currentTimeMillis(); + client.getOutputStream().write(bytes); + client.getOutputStream().flush(); + + new Thread() + { + public void run() + { + try + { + while (latch.getCount()>0) + { + // Verify echo server to client + for (byte b0 : bytes) + { + int b = in.read(); + assertTrue(b>0); + assertEquals(0xff&b0,b); + } + latch.countDown(); + } + } + catch(Throwable e) + { + System.err.println("latch="+latch.getCount()); + System.err.println("time="+(System.currentTimeMillis()-start)); + e.printStackTrace(); + } + } + }.start(); + + + // Write client to server + for (int i=1;i org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-jaspi diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java index dbdb0b4a840..526f53a0101 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java @@ -42,36 +42,38 @@ import org.eclipse.jetty.server.Authentication.User; public class JaspiAuthenticator implements Authenticator { private final ServerAuthConfig _authConfig; + private final Map _authProperties; + private final ServletCallbackHandler _callbackHandler; + private final Subject _serviceSubject; + private final boolean _allowLazyAuthentication; + private final IdentityService _identityService; + private final DeferredAuthentication _deferred; - public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, - Subject serviceSubject, boolean allowLazyAuthentication, IdentityService identityService) + public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject, + boolean allowLazyAuthentication, IdentityService identityService) { // TODO maybe pass this in via setConfiguration ? - if (callbackHandler == null) - throw new NullPointerException("No CallbackHandler"); - if (authConfig == null) - throw new NullPointerException("No AuthConfig"); + if (callbackHandler == null) throw new NullPointerException("No CallbackHandler"); + if (authConfig == null) throw new NullPointerException("No AuthConfig"); this._authConfig = authConfig; this._authProperties = authProperties; this._callbackHandler = callbackHandler; this._serviceSubject = serviceSubject; this._allowLazyAuthentication = allowLazyAuthentication; this._identityService = identityService; - this._deferred=new DeferredAuthentication(this); + this._deferred = new DeferredAuthentication(this); } - public void setConfiguration(AuthConfiguration configuration) { } - - + public String getAuthMethod() { return "JASPI"; @@ -79,56 +81,71 @@ public class JaspiAuthenticator implements Authenticator public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException { - if (_allowLazyAuthentication && !mandatory) - return _deferred; - + System.err.println("\nJaspiAuthenticator.validateRequest, uri=" + ((javax.servlet.http.HttpServletRequest) request).getRequestURI() + + " lazy=" + + _allowLazyAuthentication + + " mandatory=" + + mandatory); + new Throwable().printStackTrace(); + JaspiMessageInfo info = new JaspiMessageInfo(request, response, mandatory); - request.setAttribute("org.eclipse.jetty.security.jaspi.info",info); - return validateRequest(info); + request.setAttribute("org.eclipse.jetty.security.jaspi.info", info); + + Authentication a = validateRequest(info); + + //if its not mandatory to authenticate, and the authenticator returned UNAUTHENTICATED, we treat it as authentication deferred + if (_allowLazyAuthentication && !info.isAuthMandatory() && a == Authentication.UNAUTHENTICATED) + a =_deferred; + + System.err.println("JaspiAuthenticator.validateRequest returning "+a); + return a; } // most likely validatedUser is not needed here. public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException { - JaspiMessageInfo info = (JaspiMessageInfo)req.getAttribute("org.eclipse.jetty.security.jaspi.info"); - if (info==null) throw new NullPointerException("MeesageInfo from request missing: " + req); - return secureResponse(info,validatedUser); + System.err.println("JaspiAuthenticator.secureResponse uri=" + ((javax.servlet.http.HttpServletRequest) req).getRequestURI()); + + JaspiMessageInfo info = (JaspiMessageInfo) req.getAttribute("org.eclipse.jetty.security.jaspi.info"); + if (info == null) throw new NullPointerException("MessageInfo from request missing: " + req); + return secureResponse(info, validatedUser); } - + + public Authentication validateRequest(JaspiMessageInfo messageInfo) throws ServerAuthException { try { + System.err.println("jaspAuthenticator.validateRequest(info)"); String authContextId = _authConfig.getAuthContextID(messageInfo); - ServerAuthContext authContext = _authConfig.getAuthContext(authContextId,_serviceSubject,_authProperties); + ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); Subject clientSubject = new Subject(); - AuthStatus authStatus = authContext.validateRequest(messageInfo,clientSubject,_serviceSubject); -// String authMethod = (String)messageInfo.getMap().get(JaspiMessageInfo.AUTH_METHOD_KEY); + AuthStatus authStatus = authContext.validateRequest(messageInfo, clientSubject, _serviceSubject); + // String authMethod = + // (String)messageInfo.getMap().get(JaspiMessageInfo.AUTH_METHOD_KEY); + + if (authStatus == AuthStatus.SEND_CONTINUE) return Authentication.SEND_CONTINUE; + if (authStatus == AuthStatus.SEND_FAILURE) return Authentication.SEND_FAILURE; - if (authStatus == AuthStatus.SEND_CONTINUE) - return Authentication.SEND_CONTINUE; - if (authStatus == AuthStatus.SEND_FAILURE) - return Authentication.SEND_FAILURE; - if (authStatus == AuthStatus.SUCCESS) { - Set ids = clientSubject.getPrivateCredentials(UserIdentity.class); + Set ids = clientSubject.getPrivateCredentials(UserIdentity.class); UserIdentity userIdentity; if (ids.size() > 0) { userIdentity = ids.iterator().next(); - } else { + } + else + { CallerPrincipalCallback principalCallback = _callbackHandler.getThreadCallerPrincipalCallback(); - if (principalCallback == null) - { - return Authentication.UNAUTHENTICATED; - } + if (principalCallback == null) { return Authentication.UNAUTHENTICATED; } Principal principal = principalCallback.getPrincipal(); - if (principal == null) { + if (principal == null) + { String principalName = principalCallback.getName(); Set principals = principalCallback.getSubject().getPrincipals(); - for (Principal p: principals) + for (Principal p : principals) { if (p.getName().equals(principalName)) { @@ -136,10 +153,7 @@ public class JaspiAuthenticator implements Authenticator break; } } - if (principal == null) - { - return Authentication.UNAUTHENTICATED; - } + if (principal == null) { return Authentication.UNAUTHENTICATED; } } GroupPrincipalCallback groupPrincipalCallback = _callbackHandler.getThreadGroupPrincipalCallback(); String[] groups = groupPrincipalCallback == null ? null : groupPrincipalCallback.getGroups(); @@ -149,10 +163,10 @@ public class JaspiAuthenticator implements Authenticator } if (authStatus == AuthStatus.SEND_SUCCESS) { - //we are processing a message in a secureResponse dialog. + // we are processing a message in a secureResponse dialog. return Authentication.SEND_SUCCESS; } - //should not happen + // should not happen throw new NullPointerException("No AuthStatus returned"); } catch (AuthException e) @@ -166,13 +180,16 @@ public class JaspiAuthenticator implements Authenticator try { String authContextId = _authConfig.getAuthContextID(messageInfo); - ServerAuthContext authContext = _authConfig.getAuthContext(authContextId,_serviceSubject,_authProperties); - // TODO authContext.cleanSubject(messageInfo,validatedUser.getUserIdentity().getSubject()); - AuthStatus status = authContext.secureResponse(messageInfo,_serviceSubject); + ServerAuthContext authContext = _authConfig.getAuthContext(authContextId, _serviceSubject, _authProperties); + // TODO + // authContext.cleanSubject(messageInfo,validatedUser.getUserIdentity().getSubject()); + AuthStatus status = authContext.secureResponse(messageInfo, _serviceSubject); return (AuthStatus.SEND_SUCCESS.equals(status)); } catch (AuthException e) { + System.err.println("Error in JaspiAuthenticator.secureResponse"); + e.printStackTrace(); throw new ServerAuthException(e); } } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java index 35015090fb8..c645ffa85a2 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java @@ -98,9 +98,13 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory Subject serviceSubject=findServiceSubject(server); String serverName=findServerName(server,serviceSubject); + System.err.println("authconfigfactory="+authConfigFactory+" serviceSubject="+serviceSubject+" serverName="+serverName); String appContext = serverName + " " + context.getContextPath(); + + System.err.println("appcontext="+appContext); AuthConfigProvider authConfigProvider = authConfigFactory.getConfigProvider(MESSAGE_LAYER,appContext,listener); + System.err.println("authconfigProvider="+authConfigProvider); if (authConfigProvider != null) { ServletCallbackHandler servletCallbackHandler = new ServletCallbackHandler(loginService); diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java index d11a8d9bc84..44dbda13e3b 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java @@ -90,8 +90,9 @@ public class ServletCallbackHandler implements CallbackHandler if (user!=null) { + loginCallback.setUserPrincipal(user.getUserPrincipal()); + credentialValidationCallback.getSubject().getPrivateCredentials().add(loginCallback); credentialValidationCallback.setResult(true); - credentialValidationCallback.getSubject().getPrincipals().addAll(user.getSubject().getPrincipals()); credentialValidationCallback.getSubject().getPrivateCredentials().add(user); } diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java index e22aaa32e60..bf2c82c8c33 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/CredentialValidationCallback.java @@ -17,7 +17,7 @@ package org.eclipse.jetty.security.jaspi.callback; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; /** * CredentialValidationCallback diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java index bd66f60bf75..4eb1ad6e256 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java @@ -32,8 +32,8 @@ import javax.security.auth.message.module.ServerAuthModule; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.security.Credential; -import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.security.authentication.LoginCallbackImpl; import org.eclipse.jetty.security.jaspi.JaspiMessageInfo; import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback; @@ -90,12 +90,12 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException { // servlets do not need secured responses - return AuthStatus.SUCCESS; + return AuthStatus.SEND_SUCCESS; } public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException { - return AuthStatus.FAILURE; + return AuthStatus.SEND_FAILURE; } /** @@ -131,6 +131,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext if (credValidationCallback.getResult()) { Set loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class); + System.err.println("LoginCallbackImpls.isEmpty="+loginCallbacks.isEmpty()); if (!loginCallbacks.isEmpty()) { LoginCallbackImpl loginCallback = loginCallbacks.iterator().next(); diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java index d97c5df7152..50032d46b26 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java @@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; -import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java index 94300672200..5e637c4dedf 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java @@ -25,8 +25,8 @@ import javax.security.auth.message.MessageInfo; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.util.B64Code; /** diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java index 5702ee4892b..847b01b1c63 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java @@ -28,8 +28,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java index ab46f0ab571..be390555742 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java @@ -33,10 +33,12 @@ import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.security.CrossContextPsuedoSession; +import org.eclipse.jetty.security.authentication.DeferredAuthentication; import org.eclipse.jetty.security.authentication.LoginCallbackImpl; +import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; @@ -146,37 +148,50 @@ public class FormAuthModule extends BaseAuthModule @Override public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException { + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); - HttpSession session = request.getSession(isMandatory(messageInfo)); - String uri = request.getPathInfo(); - // not mandatory and not authenticated - if (session == null || isLoginOrErrorPage(uri)) return AuthStatus.SUCCESS; + String uri = request.getRequestURI(); + if (uri==null) + uri=URIUtil.SLASH; + + boolean mandatory = isMandatory(messageInfo); + mandatory |= isJSecurityCheck(uri); + HttpSession session = request.getSession(mandatory); + + System.err.println("FormAuthModule.validateRequest(info,subject,serviceSubject) for uri="+uri+" mandatory="+mandatory+" isLoginOrError="+isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))); + + // not mandatory or its the login or login error page don't authenticate + if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS; try { // Handle a request for authentication. - // TODO perhaps j_securitycheck can be uri suffix? - if (uri.endsWith(__J_SECURITY_CHECK)) + if (isJSecurityCheck(uri)) { - final String username = request.getParameter(__J_USERNAME); final String password = request.getParameter(__J_PASSWORD); + System.err.println("Try login username="+username+" password="+password); boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password)); if (success) { - // Redirect to original request - String nuri = (String) session.getAttribute(__J_URI); + // Redirect to original request + String nuri=null; + synchronized(session) + { + nuri = (String) session.getAttribute(__J_URI); + } + if (nuri == null || nuri.length() == 0) { nuri = request.getContextPath(); - if (nuri.length() == 0) nuri = URIUtil.SLASH; + if (nuri.length() == 0) + nuri = URIUtil.SLASH; } - session.removeAttribute(__J_URI); // Remove popped return - // URI. - response.setContentLength(0); + + System.err.println("FormAuthModule succesful login, sending redirect to "+nuri); + response.setContentLength(0); response.sendRedirect(response.encodeRedirectURL(nuri)); - return AuthStatus.SEND_CONTINUE; } // not authenticated @@ -194,95 +209,18 @@ public class FormAuthModule extends BaseAuthModule // that occur? return AuthStatus.SEND_FAILURE; } + + // Check if the session is already authenticated. FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED); - if (form_cred != null) { + System.err.println("Form cred: form.username="+form_cred._jUserName+" form.pwd="+new String(form_cred._jPassword)); + + //TODO: we would like the form auth module to be able to invoke the loginservice.validate() method to check the previously authed user + boolean success = tryLogin(messageInfo, clientSubject, response, session, form_cred._jUserName, new Password(new String(form_cred._jPassword))); if (success) { return AuthStatus.SUCCESS; } - // CallbackHandler loginCallbackHandler = new - // UserPasswordCallbackHandler(form_cred._jUserName, - // form_cred._jPassword); - // LoginResult loginResult = loginService.login(clientSubject, - // loginCallbackHandler); - // //TODO what should happen if !isMandatory but credentials - // exist and are wrong? - // if (loginResult.isSuccess()) - // { - // callbackHandler.handle(new - // Callback[]{loginResult.getCallerPrincipalCallback(), - // loginResult.getGroupPrincipalCallback()}); - // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, - // Constraint.__FORM_AUTH); - // - // form_cred = new FormCredential(form_cred._jUserName, - // form_cred._jPassword, - // loginResult.getCallerPrincipalCallback().getPrincipal()); - // - // session.setAttribute(__J_AUTHENTICATED, form_cred); - // if (ssoSource != null && ssoSource.fetch(request) == null) - // { - // UserInfo userInfo = new UserInfo(form_cred._jUserName, - // form_cred._jPassword); - // ssoSource.store(userInfo, response); - // } - // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, - // Constraint.__FORM_AUTH); - // return AuthStatus.SUCCESS; - // } - - // // We have a form credential. Has it been distributed? - // if (form_cred._userPrincipal==null) - // { - // // This form_cred appears to have been distributed. Need to - // reauth - // form_cred.authenticate(realm, request); - // - // // Sign-on to SSO mechanism - // if (form_cred._userPrincipal!=null && realm instanceof - // SSORealm) - // ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new - // Password(form_cred._jPassword)); - // - // } - // else if (!realm.reauthenticate(form_cred._userPrincipal)) - // // Else check that it is still authenticated. - // form_cred._userPrincipal=null; - // - // // If this credential is still authenticated - // if (form_cred._userPrincipal!=null) - // { - // if(LOG.isDebugEnabled())LOG.debug("FORM Authenticated for - // "+form_cred._userPrincipal.getName()); - // request.setAuthType(Constraint.__FORM_AUTH); - // //jaspi - // // request.setUserPrincipal(form_cred._userPrincipal); - // return form_cred._userPrincipal; - // } - // else - // session.setAttribute(__J_AUTHENTICATED,null); - // } - // else if (realm instanceof SSORealm) - // { - // // Try a single sign on. - // Credential cred = - // ((SSORealm)realm).getSingleSignOn(request,response); - // - // if (request.getUserPrincipal()!=null) - // { - // form_cred=new FormCredential(); - // form_cred._userPrincipal=request.getUserPrincipal(); - // form_cred._jUserName=form_cred._userPrincipal.getName(); - // if (cred!=null) - // form_cred._jPassword=cred.toString(); - // if(LOG.isDebugEnabled())LOG.debug("SSO for - // "+form_cred._userPrincipal); - // - // request.setAuthType(Constraint.__FORM_AUTH); - // session.setAttribute(__J_AUTHENTICATED,form_cred); - // return form_cred._userPrincipal; - // } } else if (ssoSource != null) { @@ -293,19 +231,25 @@ public class FormAuthModule extends BaseAuthModule if (success) { return AuthStatus.SUCCESS; } } } + + - // Don't authenticate authform or errorpage - if (!isMandatory(messageInfo) || isLoginOrErrorPage(uri)) - // TODO verify this is correct action - return AuthStatus.SUCCESS; + // if we can't send challenge + if (DeferredAuthentication.isDeferred(response)) + return AuthStatus.SUCCESS; + - // redirect to login page - if (request.getQueryString() != null) uri += "?" + request.getQueryString(); - session.setAttribute(__J_URI, request.getScheme() + "://" - + request.getServerName() - + ":" - + request.getServerPort() - + URIUtil.addPaths(request.getContextPath(), uri)); + // redirect to login page + StringBuffer buf = request.getRequestURL(); + if (request.getQueryString() != null) + buf.append("?").append(request.getQueryString()); + + synchronized (session) + { + session.setAttribute(__J_URI, buf.toString()); + } + + System.err.println("Redirecting to login page "+_formLoginPage+" and remembering juri="+buf.toString()); response.setContentLength(0); response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage))); return AuthStatus.SEND_CONTINUE; @@ -320,6 +264,20 @@ public class FormAuthModule extends BaseAuthModule } } + + /* ------------------------------------------------------------ */ + public boolean isJSecurityCheck(String uri) + { + int jsc = uri.indexOf(__J_SECURITY_CHECK); + + if (jsc<0) + return false; + int e=jsc+__J_SECURITY_CHECK.length(); + if (e==uri.length()) + return true; + char c = uri.charAt(e); + return c==';'||c=='#'||c=='/'||c=='?'; + } private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, HttpServletResponse response, HttpSession session, @@ -330,10 +288,11 @@ public class FormAuthModule extends BaseAuthModule { char[] pwdChars = password.toString().toCharArray(); Set loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class); + System.err.println("FormAuthModule, LoginCallbackImpl.isEmpty="+loginCallbacks.isEmpty()); if (!loginCallbacks.isEmpty()) { LoginCallbackImpl loginCallback = loginCallbacks.iterator().next(); - FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal()); + FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal(), loginCallback.getSubject()); session.setAttribute(__J_AUTHENTICATED, form_cred); } @@ -347,38 +306,11 @@ public class FormAuthModule extends BaseAuthModule return true; } return false; - // LoginCallback loginCallback = new LoginCallback(clientSubject, - // username, password); - // loginService.login(loginCallback); - // if (loginCallback.isSuccess()) - // { - // CallerPrincipalCallback callerPrincipalCallback = new - // CallerPrincipalCallback(clientSubject, - // loginCallback.getUserPrincipal()); - // GroupPrincipalCallback groupPrincipalCallback = new - // GroupPrincipalCallback(clientSubject, - // loginCallback.getGroups().toArray(new - // String[loginCallback.getGroups().size()])); - // callbackHandler.handle(new Callback[] {callerPrincipalCallback, - // groupPrincipalCallback}); - // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY, - // Constraint.__FORM_AUTH); - // FormCredential form_cred = new FormCredential(username, password, - // loginCallback.getUserPrincipal()); - // - // session.setAttribute(__J_AUTHENTICATED, form_cred); - // // Sign-on to SSO mechanism - // if (ssoSource != null) - // { - // UserInfo userInfo = new UserInfo(username, password); - // ssoSource.store(userInfo, response); - // } - // } - // return loginCallback.isSuccess(); } public boolean isLoginOrErrorPage(String pathInContext) { + System.err.println("ISLOGINORERRORPAGE? "+pathInContext+" error: "+_formErrorPath+" login:"+_formLoginPath); return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath)); } @@ -393,12 +325,15 @@ public class FormAuthModule extends BaseAuthModule char[] _jPassword; transient Principal _userPrincipal; + + transient Subject _subject; - private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal) + private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal, Subject subject) { this._jUserName = _jUserName; this._jPassword = _jPassword; this._userPrincipal = _userPrincipal; + this._subject = subject; } public void valueBound(HttpSessionBindingEvent event) diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml index 8f1f48b6623..4b9a0dc2a9d 100644 --- a/jetty-jmx/pom.xml +++ b/jetty-jmx/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-jmx diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx.xml b/jetty-jmx/src/main/config/etc/jetty-jmx.xml index bdce9d42981..b8c38a201d6 100644 --- a/jetty-jmx/src/main/config/etc/jetty-jmx.xml +++ b/jetty-jmx/src/main/config/etc/jetty-jmx.xml @@ -9,6 +9,17 @@ + + + + + + diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java index 5371c8b60dc..d784442d9f4 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java @@ -22,11 +22,11 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; + import javax.management.MBeanServer; import javax.management.ObjectInstance; import javax.management.ObjectName; -import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Container; @@ -41,7 +41,7 @@ import org.eclipse.jetty.util.thread.ShutdownThread; */ public class MBeanContainer extends AbstractLifeCycle implements Container.Listener, Dumpable { - private final static Logger __log = Log.getLogger(MBeanContainer.class.getName()); + private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName()); private final MBeanServer _server; private final WeakHashMap _beans = new WeakHashMap(); @@ -93,7 +93,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste } catch (Exception e) { - __log.ignore(e); + LOG.ignore(e); } } @@ -134,7 +134,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste */ public synchronized void add(Relationship relationship) { - __log.debug("add {}",relationship); + LOG.debug("add {}",relationship); ObjectName parent = _beans.get(relationship.getParent()); if (parent == null) { @@ -168,7 +168,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste */ public synchronized void remove(Relationship relationship) { - __log.debug("remove {}",relationship); + LOG.debug("remove {}",relationship); ObjectName parent = _beans.get(relationship.getParent()); ObjectName child = _beans.get(relationship.getChild()); @@ -194,7 +194,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste */ public synchronized void removeBean(Object obj) { - __log.debug("removeBean {}",obj); + LOG.debug("removeBean {}",obj); ObjectName bean = _beans.remove(obj); if (bean != null) @@ -202,7 +202,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste List beanRelations= _relations.remove(bean); if (beanRelations != null) { - __log.debug("Unregister {}", beanRelations); + LOG.debug("Unregister {}", beanRelations); List removeList = new ArrayList(beanRelations); for (Object r : removeList) { @@ -214,15 +214,15 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste try { _server.unregisterMBean(bean); - __log.debug("Unregistered {}", bean); + LOG.debug("Unregistered {}", bean); } catch (javax.management.InstanceNotFoundException e) { - __log.ignore(e); + LOG.ignore(e); } catch (Exception e) { - __log.warn(e); + LOG.warn(e); } } } @@ -234,7 +234,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste */ public synchronized void addBean(Object obj) { - __log.debug("addBean {}",obj); + LOG.debug("addBean {}",obj); try { if (obj == null || _beans.containsKey(obj)) @@ -298,13 +298,13 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste } ObjectInstance oinstance = _server.registerMBean(mbean, oname); - __log.debug("Registered {}", oinstance.getObjectName()); + LOG.debug("Registered {}", oinstance.getObjectName()); _beans.put(obj, oinstance.getObjectName()); } catch (Exception e) { - __log.warn("bean: " + obj, e); + LOG.warn("bean: " + obj, e); } } diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java index 08797aa7264..8b309d5ce67 100644 --- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java +++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java @@ -28,8 +28,6 @@ import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.management.Attribute; import javax.management.AttributeList; diff --git a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ConnectorServerTest.java b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ConnectorServerTest.java index 10ed7d822ac..1818567d25c 100644 --- a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ConnectorServerTest.java +++ b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ConnectorServerTest.java @@ -16,8 +16,6 @@ package org.eclipse.jetty.jmx; -import java.net.MalformedURLException; - import javax.management.remote.JMXServiceURL; import org.junit.Test; diff --git a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java index 4b1d09f79d8..ebb5f63defb 100644 --- a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java +++ b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java @@ -13,10 +13,11 @@ package org.eclipse.jetty.jmx; -import com.acme.Derived; +import static org.junit.Assert.assertTrue; + import org.junit.Test; -import static org.junit.Assert.assertTrue; +import com.acme.Derived; public class ObjectMBeanTest { diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index 2f3977820b8..5eaf6fef291 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-jndi diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java new file mode 100644 index 00000000000..14a2b8fe35d --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2011 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.jndi; + +import java.util.Iterator; + +import javax.naming.Binding; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +/** BindingEnumeration + *

    Implementation of NamingEnumeration + * + *

    Notes

    + *

    Used to return results of Context.listBindings(); + * + *

    Usage

    + * + */ +public class BindingEnumeration implements NamingEnumeration +{ + Iterator _delegate; + + public BindingEnumeration (Iterator e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasNext(); + } + + public Binding next() + throws NamingException + { + Binding b = (Binding)_delegate.next(); + return new Binding (b.getName(), b.getClassName(), b.getObject(), true); + } + + public boolean hasMoreElements() + { + return _delegate.hasNext(); + } + + public Binding nextElement() + { + Binding b = (Binding)_delegate.next(); + return new Binding (b.getName(), b.getClassName(), b.getObject(),true); + } +} \ No newline at end of file diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java new file mode 100644 index 00000000000..432fa65f3df --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2011 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.jndi; + +import java.util.Iterator; + +import javax.naming.Binding; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +/** NameEnumeration + *

    Implementation of NamingEnumeration interface. + * + *

    Notes

    + *

    Used for returning results of Context.list(); + * + *

    Usage

    + * + */ +public class NameEnumeration implements NamingEnumeration +{ + Iterator _delegate; + + public NameEnumeration (Iterator e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasNext(); + } + + public NameClassPair next() + throws NamingException + { + Binding b = _delegate.next(); + return new NameClassPair(b.getName(),b.getClassName(),true); + } + + public boolean hasMoreElements() + { + return _delegate.hasNext(); + } + + public NameClassPair nextElement() + { + Binding b = _delegate.next(); + return new NameClassPair(b.getName(),b.getClassName(),true); + } +} \ No newline at end of file diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java index e6dec8bbae0..da2ee7d35b0 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,7 +30,6 @@ import javax.naming.InitialContext; import javax.naming.LinkRef; import javax.naming.Name; import javax.naming.NameAlreadyBoundException; -import javax.naming.NameClassPair; import javax.naming.NameNotFoundException; import javax.naming.NameParser; import javax.naming.NamingEnumeration; @@ -53,17 +51,8 @@ import org.eclipse.jetty.util.log.Logger; *

    Notes

    *

    All Names are expected to be Compound, not Composite. * - *

    Usage

    - *
    + * 
      */
    -/*
    -* 
    -* -* @see -* -* -* @version 1.0 -*/ public class NamingContext implements Context, Cloneable, Dumpable { private final static Logger __log=NamingUtil.__log; @@ -101,123 +90,6 @@ public class NamingContext implements Context, Cloneable, Dumpable void unbind(NamingContext ctx, Binding binding); } - /*------------------------------------------------*/ - /** NameEnumeration - *

    Implementation of NamingEnumeration interface. - * - *

    Notes

    - *

    Used for returning results of Context.list(); - * - *

    Usage

    - *
    -     */
    -    /*
    -     * 
    - * - * @see - * - */ - public class NameEnumeration implements NamingEnumeration - { - Iterator _delegate; - - public NameEnumeration (Iterator e) - { - _delegate = e; - } - - public void close() - throws NamingException - { - } - - public boolean hasMore () - throws NamingException - { - return _delegate.hasNext(); - } - - public NameClassPair next() - throws NamingException - { - Binding b = _delegate.next(); - return new NameClassPair(b.getName(),b.getClassName(),true); - } - - public boolean hasMoreElements() - { - return _delegate.hasNext(); - } - - public NameClassPair nextElement() - { - Binding b = _delegate.next(); - return new NameClassPair(b.getName(),b.getClassName(),true); - } - } - - - - - - - /*------------------------------------------------*/ - /** BindingEnumeration - *

    Implementation of NamingEnumeration - * - *

    Notes

    - *

    Used to return results of Context.listBindings(); - * - *

    Usage

    - *
    -     */
    -    /*
    -     * 
    - * - * @see - * - */ - public class BindingEnumeration implements NamingEnumeration - { - Iterator _delegate; - - public BindingEnumeration (Iterator e) - { - _delegate = e; - } - - public void close() - throws NamingException - { - } - - public boolean hasMore () - throws NamingException - { - return _delegate.hasNext(); - } - - public Binding next() - throws NamingException - { - Binding b = (Binding)_delegate.next(); - return new Binding (b.getName(), b.getClassName(), b.getObject(), true); - } - - public boolean hasMoreElements() - { - return _delegate.hasNext(); - } - - public Binding nextElement() - { - Binding b = (Binding)_delegate.next(); - return new Binding (b.getName(), b.getClassName(), b.getObject(),true); - } - } - - - /*------------------------------------------------*/ /** * Constructor @@ -240,26 +112,6 @@ public class NamingContext implements Context, Cloneable, Dumpable } - /*------------------------------------------------*/ - /** - * Creates a new NamingContext instance. - * - * @param env a Hashtable value - */ - public NamingContext (Hashtable env) - { - if (env != null) - _env.putAll(env); - } - - /*------------------------------------------------*/ - /** - * Constructor - * - */ - public NamingContext () - { - } /*------------------------------------------------*/ @@ -312,8 +164,24 @@ public class NamingContext implements Context, Cloneable, Dumpable _parser = parser; } + + public void setEnv (Hashtable env) + { + _env.clear(); + _env.putAll(env); + } + + public Map getBindings () + { + return _bindings; + } + public void setBindings(Map bindings) + { + _bindings = bindings; + } + /*------------------------------------------------*/ /** * Bind a name to an object @@ -435,8 +303,6 @@ public class NamingContext implements Context, Cloneable, Dumpable ne.setRemainingName(name); throw ne; } - - Name cname = toCanonicalName (name); @@ -521,7 +387,7 @@ public class NamingContext implements Context, Cloneable, Dumpable /*------------------------------------------------*/ /** - * Not supported + * * * @param name name of subcontext to remove * @exception NamingException if an error occurs @@ -536,7 +402,7 @@ public class NamingContext implements Context, Cloneable, Dumpable /*------------------------------------------------*/ /** - * Not supported + * * * @param name name of subcontext to remove * @exception NamingException if an error occurs @@ -1128,7 +994,6 @@ public class NamingContext implements Context, Cloneable, Dumpable ctx = binding.getObject(); - if (ctx instanceof Reference) { //deference the object @@ -1154,8 +1019,7 @@ public class NamingContext implements Context, Cloneable, Dumpable } else throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); - } - + } } /*------------------------------------------------*/ @@ -1182,11 +1046,11 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param newName a Name value * @exception NamingException if an error occurs */ public void rename(String oldName, - String newName) - throws NamingException - { - throw new OperationNotSupportedException(); - } + String newName) + throws NamingException + { + throw new OperationNotSupportedException(); + } @@ -1247,9 +1111,7 @@ public class NamingContext implements Context, Cloneable, Dumpable */ public void close () throws NamingException - { - - + { } @@ -1362,7 +1224,7 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name a Name value * @param obj an Object value */ - protected void addBinding (Name name, Object obj) throws NameAlreadyBoundException + public void addBinding (Name name, Object obj) throws NameAlreadyBoundException { String key = name.toString(); Binding binding=new Binding (key, obj); @@ -1394,7 +1256,7 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name a Name value * @return a Binding value */ - protected Binding getBinding (Name name) + public Binding getBinding (Name name) { return (Binding) _bindings.get(name.toString()); } @@ -1407,13 +1269,13 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name as a String * @return null or the Binding */ - protected Binding getBinding (String name) + public Binding getBinding (String name) { return (Binding) _bindings.get(name); } /*------------------------------------------------*/ - protected void removeBinding (Name name) + public void removeBinding (Name name) { String key = name.toString(); if (__log.isDebugEnabled()) @@ -1455,7 +1317,7 @@ public class NamingContext implements Context, Cloneable, Dumpable } /* ------------------------------------------------------------ */ - private boolean isLocked() + public boolean isLocked() { if ((_env.get(LOCK_PROPERTY) == null) && (_env.get(UNLOCK_PROPERTY) == null)) return false; diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java index e6ff384f548..210b52d2a0d 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java @@ -30,7 +30,7 @@ import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; -import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.util.security.Password; /** * MailSessionReference diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java index 09dcfc8f49a..d26b8475b18 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java @@ -67,8 +67,7 @@ public class javaRootURLContext implements Context try { __javaNameParser = new javaNameParser(); - __nameRoot = new NamingContext(); - __nameRoot.setNameParser(__javaNameParser); + __nameRoot = new NamingContext(null,null,null,__javaNameParser); StringRefAddr parserAddr = new StringRefAddr("parser", __javaNameParser.getClass().getName()); diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java index 7c187a7166e..a915ad1917d 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java @@ -13,36 +13,63 @@ package org.eclipse.jetty.jndi.local; +import java.util.Collections; +import java.util.HashMap; import java.util.Hashtable; +import java.util.List; +import java.util.Map; import java.util.Properties; +import javax.naming.Binding; import javax.naming.CompoundName; import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; import javax.naming.Name; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; +import javax.naming.NotContextException; +import javax.naming.OperationNotSupportedException; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.spi.NamingManager; +import org.eclipse.jetty.jndi.BindingEnumeration; +import org.eclipse.jetty.jndi.NameEnumeration; import org.eclipse.jetty.jndi.NamingContext; +import org.eclipse.jetty.jndi.NamingUtil; +import org.eclipse.jetty.util.log.Logger; /** * * localContext * + * Implementation of the delegate for InitialContext for the local namespace. + * * * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $ * */ public class localContextRoot implements Context { - private static final NamingContext __root = new NamingContext(); + private final static Logger __log=NamingUtil.__log; + protected final static NamingContext __root = new NamingRoot(); private final Hashtable _env; + - // make a root for the static namespace local: - static + static class NamingRoot extends NamingContext { - __root.setNameParser(new LocalNameParser()); + public NamingRoot() + { + super (null,null,null,new LocalNameParser()); + } } + + static class LocalNameParser implements NameParser { @@ -61,6 +88,19 @@ public class localContextRoot implements Context } } + + /* + * Root has to use the localContextRoot's env for all operations. + * So, if createSubcontext in the root, use the env of the localContextRoot. + * If lookup binding in the root, use the env of the localContextRoot. + * + */ + + + + + + public static NamingContext getRoot() { return __root; @@ -91,6 +131,21 @@ public class localContextRoot implements Context return ""; } + + /** + * + * + * @see javax.naming.Context#destroySubcontext(javax.naming.Name) + */ + public void destroySubcontext(Name name) throws NamingException + { + synchronized (__root) + { + __root.destroySubcontext(getSuffix(name)); + } + } + + /** * * @@ -100,23 +155,12 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.destroySubcontext(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#unbind(java.lang.String) - */ - public void unbind(String name) throws NamingException - { - synchronized (__root) - { - __root.unbind(getSuffix(name)); + + destroySubcontext(__root.getNameParser("").parse(getSuffix(name))); } } + /** * * @@ -127,18 +171,7 @@ public class localContextRoot implements Context return _env; } - /** - * - * - * @see javax.naming.Context#destroySubcontext(javax.naming.Name) - */ - public void destroySubcontext(Name name) throws NamingException - { - synchronized (__root) - { - __root.destroySubcontext(getSuffix(name)); - } - } + /** * @@ -149,23 +182,92 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.unbind(getSuffix(name)); + //__root.unbind(getSuffix(name)); + + if (name.size() == 0) + return; + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just unbind it + if (cname.size() == 1) + { + __root.removeBinding (cname); + } + else + { + //walk down the subcontext hierarchy + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).unbind (cname.getSuffix(1)); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + + + } } /** * * - * @see javax.naming.Context#lookup(java.lang.String) + * @see javax.naming.Context#unbind(java.lang.String) */ - public Object lookup(String name) throws NamingException + public void unbind(String name) throws NamingException { - synchronized (__root) - { - return __root.lookup(getSuffix(name)); - } + unbind(__root.getNameParser("").parse(getSuffix(name))); } + + /** * * @@ -175,56 +277,7 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.lookupLink(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#removeFromEnvironment(java.lang.String) - */ - public Object removeFromEnvironment(String propName) throws NamingException - { - return _env.remove(propName); - } - - /** - * - * - * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) - */ - public void bind(String name, Object obj) throws NamingException - { - synchronized (__root) - { - __root.bind(getSuffix(name), obj); - } - } - - /** - * - * - * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) - */ - public void rebind(String name, Object obj) throws NamingException - { - synchronized (__root) - { - __root.rebind(getSuffix(name), obj); - } - } - - /** - * - * - * @see javax.naming.Context#lookup(javax.naming.Name) - */ - public Object lookup(Name name) throws NamingException - { - synchronized (__root) - { - return __root.lookup(getSuffix(name)); + return lookupLink(__root.getNameParser("").parse(getSuffix(name))); } } @@ -237,10 +290,262 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.lookupLink(getSuffix(name)); + //return __root.lookupLink(getSuffix(name)); + + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + { + //If no name create copy of this context with same bindings, but with copy of the environment so it can be modified + NamingContext ctx = new NamingContext (_env, null, null, __root.getNameParser("")); + ctx.setBindings(__root.getBindings()); + return ctx; + } + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + Binding binding = __root.getBinding (cname); + if (binding == null) + throw new NameNotFoundException(); + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname.getPrefix(1), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + { + //object is either a LinkRef which we don't dereference + //or a plain object in which case spec says we return it + return o; + } + } + + + //it is a multipart name, recurse to the first subcontext + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + + } } + + /** + * + * + * @see javax.naming.Context#removeFromEnvironment(java.lang.String) + */ + public Object removeFromEnvironment(String propName) throws NamingException + { + return _env.remove(propName); + } + + + /** + * + * + * @see javax.naming.Context#lookup(javax.naming.Name) + */ + public Object lookup(Name name) throws NamingException + { + synchronized (__root) + { + //return __root.lookup(getSuffix(name)); + + if(__log.isDebugEnabled())__log.debug("Looking up name=\""+name+"\""); + Name cname = __root.toCanonicalName(name); + + if ((cname == null) || (cname.size() == 0)) + { + __log.debug("Null or empty name, returning copy of this context"); + NamingContext ctx = new NamingContext (_env, null, null, __root.getNameParser("")); + ctx.setBindings(__root.getBindings()); + return ctx; + } + + + + if (cname.size() == 1) + { + Binding binding = __root.getBinding (cname); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof LinkRef) + { + //if link name starts with ./ it is relative to current context + String linkName = ((LinkRef)o).getLinkName(); + if (linkName.startsWith("./")) + return lookup (linkName.substring(2)); + else + { + //link name is absolute + InitialContext ictx = new InitialContext(); + return ictx.lookup (linkName); + } + } + else if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname, __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + return o; + } + + //it is a multipart name, get the first subcontext + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + //as we have bound a reference to an object factory + //for the component specific contexts + //at "comp" we need to resolve the reference + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + + } + } + + + /** + * + * + * @see javax.naming.Context#lookup(java.lang.String) + */ + public Object lookup(String name) throws NamingException + { + synchronized (__root) + { + return lookup(__root.getNameParser("").parse(getSuffix(name))); + } + } + + + /** + * + * + * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) + */ + public void bind(String name, Object obj) throws NamingException + { + synchronized (__root) + { + bind(__root.getNameParser("").parse(getSuffix(name)), obj); + + } + } + + /** * * @@ -250,7 +555,83 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.bind(getSuffix(name), obj); + // __root.bind(getSuffix(name), obj); + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //get the object to be bound + Object objToBind = NamingManager.getStateToBind(obj, name,this, _env); + // Check for Referenceable + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + + //anything else we should be able to bind directly + __root.addBinding (cname, objToBind); + } + else + { + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + //walk down the subcontext hierarchy + //need to ignore trailing empty "" name components + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent+ " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + + if (ctx instanceof Context) + { + ((Context)ctx).bind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } } } @@ -263,7 +644,105 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.rebind(getSuffix(name), obj); + //__root.rebind(getSuffix(name), obj); + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //check if it is a Referenceable + Object objToBind = NamingManager.getStateToBind(obj, name, __root, _env); + + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + __root.removeBinding(cname); + __root.addBinding (cname, objToBind); + } + else + { + //walk down the subcontext hierarchy + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).rebind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + } + } + + /** + * + * + * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) + */ + public void rebind(String name, Object obj) throws NamingException + { + synchronized (__root) + { + rebind(__root.getNameParser("").parse(getSuffix(name)), obj); + } + } + /** + * + * + * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) + */ + public void rename(Name oldName, Name newName) throws NamingException + { + synchronized (__root) + { + throw new OperationNotSupportedException(); } } @@ -276,7 +755,7 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.rename(getSuffix(oldName), getSuffix(newName)); + throw new OperationNotSupportedException(); } } @@ -289,7 +768,15 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.createSubcontext(getSuffix(name)); + //if the subcontext comes directly off the root, use the env of the InitialContext + //as the root itself has no environment. Otherwise, it inherits the env of the parent + //Context further down the tree. + //NamingContext ctx = (NamingContext)__root.createSubcontext(name); + //if (ctx.getParent() == __root) + // ctx.setEnv(_env); + //return ctx; + + return createSubcontext(__root.getNameParser("").parse(name)); } } @@ -301,24 +788,92 @@ public class localContextRoot implements Context public Context createSubcontext(Name name) throws NamingException { synchronized (__root) - { - return __root.createSubcontext(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) - */ - public void rename(Name oldName, Name newName) throws NamingException - { - synchronized (__root) - { - __root.rename(getSuffix(oldName), getSuffix(newName)); + { + //if the subcontext comes directly off the root, use the env of the InitialContext + //as the root itself has no environment. Otherwise, it inherits the env of the parent + //Context further down the tree. + //NamingContext ctx = (NamingContext)__root.createSubcontext(getSuffix(name)); + //if (ctx.getParent() == __root) + // ctx.setEnv(_env); + //return ctx; + + + + + if (__root.isLocked()) + { + NamingException ne = new NamingException ("This context is immutable"); + ne.setRemainingName(name); + throw ne; + } + + Name cname = __root.toCanonicalName (name); + + if (cname == null) + throw new NamingException ("Name is null"); + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + //not permitted to bind if something already bound at that name + Binding binding = __root.getBinding (cname); + if (binding != null) + throw new NameAlreadyBoundException (cname.toString()); + + //make a new naming context with the root as the parent + Context ctx = new NamingContext ((Hashtable)_env.clone(), cname.get(0), __root, __root.getNameParser("")); + __root.addBinding (cname, ctx); + return ctx; + } + + + //If the name has multiple subcontexts, walk the hierarchy by + //fetching the first one. All intermediate subcontexts in the + //name must already exist. + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent + " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(__log.isDebugEnabled())__log.debug("Object bound at "+firstComponent +" is a Reference"); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + return ((Context)ctx).createSubcontext (cname.getSuffix(1)); + } + else + throw new NotContextException (firstComponent +" is not a Context"); } } + /** * * @@ -348,22 +903,10 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.list(getSuffix(name)); + return list(__root.getNameParser("").parse(getSuffix(name))); } } - /** - * - * - * @see javax.naming.Context#listBindings(java.lang.String) - */ - public NamingEnumeration listBindings(String name) throws NamingException - { - synchronized (__root) - { - return __root.listBindings(getSuffix(name)); - } - } /** * @@ -374,7 +917,64 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.list(getSuffix(name)); + //return __root.list(getSuffix(name)); + + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + { + List empty = Collections.emptyList(); + return new NameEnumeration(empty.iterator()); + } + + + if (cname.size() == 0) + { + return new NameEnumeration (__root.getBindings().values().iterator()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(__log.isDebugEnabled())__log.debug("Dereferencing Reference for "+name.get(0)); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).list (cname.getSuffix(1)); + } } @@ -387,10 +987,82 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.listBindings(getSuffix(name)); + //return __root.listBindings(getSuffix(name)); + + Name cname = __root.toCanonicalName (name); + + if (cname == null) + { + List empty = Collections.emptyList(); + return new BindingEnumeration(empty.iterator()); + } + + if (cname.size() == 0) + { + return new BindingEnumeration (__root.getBindings().values().iterator()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + //if a name has a leading "/" it is parsed as "" so ignore it by staying + //at this level in the tree + if (firstComponent.equals("")) + ctx = this; + else + { + //it is a non-empty name component + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).listBindings (cname.getSuffix(1)); + } } + + /** + * + * + * @see javax.naming.Context#listBindings(java.lang.String) + */ + public NamingEnumeration listBindings(String name) throws NamingException + { + synchronized (__root) + { + return listBindings(__root.getNameParser("").parse(getSuffix(name))); + } + } + + /** * * diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java index 0caa18fcefd..d41539de51d 100644 --- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java @@ -12,11 +12,19 @@ // ======================================================================== package org.eclipse.jetty.jndi.java; +import java.util.Hashtable; + import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NameNotFoundException; import javax.naming.NameParser; +import javax.naming.NamingException; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; import org.eclipse.jetty.jndi.NamingUtil; import org.junit.After; @@ -31,13 +39,152 @@ import static org.junit.Assert.fail; */ public class TestLocalJNDI { + public static class FruitFactory implements ObjectFactory + { + public FruitFactory() + { + } + + public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception + { + + if (!env.containsKey("flavour")) + throw new Exception ("No flavour!"); + + if (obj instanceof Reference) + { + Reference ref = (Reference)obj; + if (ref.getClassName().equals(Fruit.class.getName())) + { + RefAddr addr = ref.get("fruit"); + if (addr != null) + { + return new Fruit((String)addr.getContent()); + } + } + } + return null; + } + } + + + public static class Fruit implements Referenceable + { + String fruit; + + public Fruit(String f) + { + fruit = f; + } + + public Reference getReference() throws NamingException + { + return new Reference( + Fruit.class.getName(), + new StringRefAddr("fruit", fruit), + FruitFactory.class.getName(), + null); // Factory location + } + + public String toString() + { + return fruit; + } + } + + + + + + + + @After public void tearDown() throws Exception { InitialContext ic = new InitialContext(); ic.destroySubcontext("a"); } + + + @Test + public void testLocalReferenceable() throws Exception + { + Hashtable env1 = new Hashtable(); + env1.put("flavour", "orange"); + InitialContext ic1 = new InitialContext(env1); + + ic1.bind("valencia", new Fruit("orange")); + + Object o = ic1.lookup("valencia"); + Hashtable env2 = new Hashtable(); + InitialContext ic2 = new InitialContext(env2); + try + { + o = ic2.lookup("valencia"); + fail("Constructed object from reference without correct environment"); + } + catch (Exception e) + { + assertEquals("No flavour!", e.getMessage()); + } + } + + + @Test + public void testLocalEnvironment() throws Exception + { + Hashtable env1 = new Hashtable(); + env1.put("make", "holden"); + env1.put("model", "commodore"); + + Object car1 = new Object(); + + InitialContext ic = new InitialContext(env1); + ic.bind("car1", car1); + assertNotNull(ic.lookup("car1")); + assertEquals(car1, ic.lookup("car1")); + + Context carz = ic.createSubcontext("carz"); + assertNotNull(carz); + Hashtable ht = carz.getEnvironment(); + assertNotNull(ht); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + + Hashtable env2 = new Hashtable(); + env2.put("flavour", "strawberry"); + InitialContext ic2 = new InitialContext(env2); + assertEquals(car1, ic2.lookup("car1")); + Context c = (Context)ic2.lookup("carz"); + assertNotNull(c); + ht = c.getEnvironment(); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + + Context icecreamz = ic2.createSubcontext("icecreamz"); + ht = icecreamz.getEnvironment(); + assertNotNull(ht); + assertEquals("strawberry", ht.get("flavour")); + + Context hatchbackz = ic2.createSubcontext("carz/hatchbackz"); + assertNotNull(hatchbackz); + ht = hatchbackz.getEnvironment(); + assertNotNull(ht); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + assertEquals(null, ht.get("flavour")); + + c = (Context)ic.lookup("carz/hatchbackz"); + assertNotNull(c); + assertEquals(hatchbackz, c); + + } + + + + @Test public void testLocal () throws Exception { diff --git a/jetty-jsp-2.1/src/main/java/org/eclipse/jetty/jsp/JettyLog.java b/jetty-jsp-2.1/src/main/java/org/eclipse/jetty/jsp/JettyLog.java deleted file mode 100644 index e8bd4afe952..00000000000 --- a/jetty-jsp-2.1/src/main/java/org/eclipse/jetty/jsp/JettyLog.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.eclipse.jetty.jsp; - -import com.sun.org.apache.commons.logging.LogFactory; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; - -/** - * @version $Revision$ $Date$ - */ -public class JettyLog implements com.sun.org.apache.commons.logging.Log -{ - private static volatile boolean __initialized; - - /** - * Called via reflection from WebXmlProcessor - */ - public static synchronized void init() - { - if (!__initialized) - { - __initialized = true; - LogFactory.setLogImplClassName(JettyLog.class.getName()); - } - } - - private final Logger _logger; - - public JettyLog(String name) - { - _logger = Log.getLogger(name); - } - - public void fatal(Object o) - { - error(o); - } - - public void fatal(Object o, Throwable throwable) - { - error(o, throwable); - } - - public boolean isErrorEnabled() - { - return true; - } - - public void error(Object o) - { - warn(o); - } - - public void error(Object o, Throwable throwable) - { - _logger.warn(String.valueOf(o), throwable); - } - - public boolean isWarnEnabled() - { - return true; - } - - public void warn(Object o) - { - _logger.warn(String.valueOf(o)); - } - - public boolean isInfoEnabled() - { - return true; - } - - public void info(Object o) - { - _logger.info(String.valueOf(o)); - } - - public boolean isDebugEnabled() - { - return _logger.isDebugEnabled(); - } - - public void debug(Object o) - { - _logger.debug(String.valueOf(o)); - } - - public void debug(Object o, Throwable throwable) - { - _logger.debug(String.valueOf(o), throwable); - } - - public boolean isTraceEnabled() - { - return isDebugEnabled(); - } - - public void trace(Object o) - { - debug(o); - } -} diff --git a/jetty-monitor/pom.xml b/jetty-monitor/pom.xml index 25d0fa5ac7f..bfff477df54 100644 --- a/jetty-monitor/pom.xml +++ b/jetty-monitor/pom.xml @@ -19,7 +19,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-monitor @@ -124,6 +124,12 @@ ${project.version} test + + org.eclipse.jetty + jetty-websocket + ${project.version} + test + org.eclipse.jetty jetty-server diff --git a/jetty-nested/pom.xml b/jetty-nested/pom.xml index f331004206b..40da79b3cfe 100644 --- a/jetty-nested/pom.xml +++ b/jetty-nested/pom.xml @@ -4,7 +4,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT jetty-nested Jetty :: Nested diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java index 045afc17e35..0d98785a526 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.nested; import java.io.IOException; import java.util.Enumeration; -import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; @@ -24,14 +23,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.DispatcherType; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.util.log.Log; -public class NestedConnection extends HttpConnection +public class NestedConnection extends AbstractHttpConnection { protected NestedConnection(final NestedConnector connector, final NestedEndPoint endp, final HttpServletRequest outerRequest, HttpServletResponse outerResponse,String nestedIn) throws IOException @@ -88,7 +84,8 @@ public class NestedConnection extends HttpConnection { getServer().handle(this); completeResponse(); - _generator.flushBuffer(); + while (!_generator.isComplete() && _endp.isOpen()) + _generator.flushBuffer(); _endp.flush(); } finally diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java index 30d7fbb7907..efe0d4a801d 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java @@ -4,29 +4,25 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.nested; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.bio.StreamEndPoint; public class NestedEndPoint extends StreamEndPoint { private final HttpServletRequest _outerRequest; - + public NestedEndPoint(HttpServletRequest outerRequest, HttpServletResponse outerResponse) throws IOException { @@ -65,7 +61,6 @@ public class NestedEndPoint extends StreamEndPoint @Override public String getRemoteHost() { - // TODO Auto-generated method stub return _outerRequest.getRemoteHost(); } @Override diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java index 3ab098ccf1c..47d74c3d1ee 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java @@ -43,7 +43,7 @@ public class NestedGenerator extends AbstractGenerator public void addContent(Buffer content, boolean last) throws IOException { - // LOG.debug("addContent {} {}",content.length(),last); + LOG.debug("addContent {} {}",content.length(),last); if (_noContent) { content.clear(); @@ -70,7 +70,6 @@ public class NestedGenerator extends AbstractGenerator // Handle any unfinished business? if (_content != null && _content.length() > 0) { - flushBuffer(); if (_content != null && _content.length() > 0) throw new IllegalStateException("FULL"); @@ -86,20 +85,22 @@ public class NestedGenerator extends AbstractGenerator content.clear(); _content = null; } - else + else if (!last || _buffer!=null) { // Yes - so we better check we have a buffer - initContent(); + initBuffer(); // Copy _content to buffer; int len = 0; len = _buffer.put(_content); - // make sure there is space for a trailing null + // make sure there is space for a trailing null (???) if (len > 0 && _buffer.space() == 0) { len--; _buffer.setPutIndex(_buffer.putIndex() - 1); } + + LOG.debug("copied {} to buffer",len); _content.skip(len); @@ -139,7 +140,7 @@ public class NestedGenerator extends AbstractGenerator return false; // we better check we have a buffer - initContent(); + initBuffer(); // Copy _content to buffer; @@ -149,7 +150,7 @@ public class NestedGenerator extends AbstractGenerator } /* ------------------------------------------------------------ */ - private void initContent() throws IOException + private void initBuffer() throws IOException { if (_buffer == null) { @@ -176,7 +177,7 @@ public class NestedGenerator extends AbstractGenerator @Override public int prepareUncheckedAddContent() throws IOException { - initContent(); + initBuffer(); return _buffer.space(); } @@ -230,29 +231,70 @@ public class NestedGenerator extends AbstractGenerator } /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ @Override - public long flushBuffer() throws IOException + public void complete() throws IOException + { + if (_state == STATE_END) + return; + + super.complete(); + + if (_state < STATE_FLUSHING) + _state = STATE_FLUSHING; + + flushBuffer(); + } + + /* ------------------------------------------------------------ */ + @Override + public int flushBuffer() throws IOException { if (_state == STATE_HEADER) throw new IllegalStateException("State==HEADER"); - - if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) - { - initContent(); - _buffer.put(_content); - _content.clear(); - _content = null; - } + int len = 0; if (_buffer==null) - return 0; + { + + if (_content!=null && _content.length()>0) + { + // flush content directly + len = _endp.flush(_content); + if (len>0) + _content.skip(len); + } + } + else + { + if (_buffer.length()==0 && _content!=null && _content.length()>0) + { + // Copy content to buffer + _content.skip(_buffer.put(_content)); + } + + int size=_buffer.length(); + len =_endp.flush(_buffer); + LOG.debug("flushBuffer {} of {}",len,size); + if (len>0) + _buffer.skip(len); + } - int size=_buffer.length(); - int len = _buffer==null?0:_endp.flush(_buffer); - LOG.debug("flushBuffer {} of {}",len,size); - if (len>0) - _buffer.skip(len); + if (_content!=null && _content.length()==0) + _content=null; + if (_buffer!=null && _buffer.length()==0 && _content==null) + { + _buffers.returnBuffer(_buffer); + _buffer=null; + } + + if (_state==STATE_FLUSHING && _buffer==null && _content==null) + _state=STATE_END; return len; } diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java index 1b6830ffbd4..9bac8831296 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java @@ -38,9 +38,9 @@ public class NestedParser implements Parser return false; } - public int parseAvailable() throws IOException + public boolean parseAvailable() throws IOException { - return 0; + return false; } public boolean isMoreInBuffer() throws IOException @@ -53,4 +53,13 @@ public class NestedParser implements Parser return false; } + public boolean isPersistent() + { + return false; + } + + public void setPersistent(boolean persistent) + { + } + } diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index d1e205970be..75b77f019a9 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-nosql diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java index cceb8551032..4790834199e 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java @@ -428,7 +428,6 @@ public class MongoSessionManager extends NoSqlSessionManager return o; } - bout.reset(); out.reset(); out.writeUnshared(value); out.flush(); diff --git a/jetty-nosql/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java b/jetty-nosql/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java index 5bda287bf29..269cf4c710a 100644 --- a/jetty-nosql/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java +++ b/jetty-nosql/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java @@ -52,7 +52,7 @@ public class MongoTestServer extends AbstractTestServer _saveAllAttributes = saveAllAttributes; } - public SessionIdManager newSessionIdManager() + public SessionIdManager newSessionIdManager(String config) { if ( _idManager != null ) { diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml index 3e36a0eb7dc..76e530cde78 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java index 46a9ce31bc3..6e34d4a5902 100644 --- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java +++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java @@ -36,6 +36,7 @@ public class FragmentActivator implements BundleActivator * */ public void start(BundleContext context) throws Exception { + System.setProperty("org.apache.jasper.compiler.disablejsr199", Boolean.TRUE.toString()); WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS.add(new WebappRegistrationCustomizerImpl()); WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS.add(new PluggableWebAppRegistrationCustomizerImpl()); } diff --git a/jetty-osgi/jetty-osgi-boot-logback/pom.xml b/jetty-osgi/jetty-osgi-boot-logback/pom.xml index e6beaefd5af..595e77a4b24 100644 --- a/jetty-osgi/jetty-osgi-boot-logback/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-logback/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index e7b3480acfd..a0463cc5643 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index adcef5d8f18..87a7087125d 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java index bb89bfe4889..21d0d6adb7d 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -271,7 +271,8 @@ public class ServerInstanceWrapper { try { // Execute a Jetty configuration file - is = jettyConfiguration.openStream(); + Resource r = Resource.newResource(jettyConfiguration); + is = r.getInputStream(); XmlConfiguration config = new XmlConfiguration(is); config.getIdMap().putAll(id_map); diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java index 27068cb1167..ea8fc5192b9 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java @@ -376,7 +376,8 @@ public class WebBundleDeployerHelper implements IWebBundleDeployerHelper URL contextURL = contributor.getEntry(contextFileRelativePath); if (contextURL != null) { - return registerContext(contributor,contextFileRelativePath,contextURL.openStream(),extraClasspath,overrideBundleInstallLocation,requireTldBundle,handler); + Resource r = Resource.newResource(contextURL); + return registerContext(contributor,contextFileRelativePath,r.getInputStream(),extraClasspath,overrideBundleInstallLocation,requireTldBundle,handler); } throw new IllegalArgumentException("Could not find the context " + "file " + contextFileRelativePath + " for the bundle " + contributor.getSymbolicName() + (overrideBundleInstallLocation != null?" using the install location " + overrideBundleInstallLocation:"")); diff --git a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml b/jetty-osgi/jetty-osgi-equinoxtools/pom.xml index e45de577294..fbb16968697 100644 --- a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml +++ b/jetty-osgi/jetty-osgi-equinoxtools/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 959ae2ad6ab..e389254295d 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-osgi/jetty-osgi-servletbridge/pom.xml b/jetty-osgi/jetty-osgi-servletbridge/pom.xml index 1fe0789fe43..703550a8ec6 100644 --- a/jetty-osgi/jetty-osgi-servletbridge/pom.xml +++ b/jetty-osgi/jetty-osgi-servletbridge/pom.xml @@ -2,7 +2,8 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.4.3-SNAPSHOT + + 7.5.2-SNAPSHOT ../pom.xml 4.0.0 @@ -11,6 +12,7 @@ Jetty :: OSGi :: Servletbridge Jetty OSGi Servletbridge webapp war + false org.eclipse.equinox @@ -37,4 +39,15 @@ http://intalio.org/public/maven2 + + + + org.apache.maven.plugins + maven-eclipse-plugin + + false + + + + diff --git a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/nested/Dump.java b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/nested/Dump.java index 9a178a2e834..dbb0e2a78c0 100644 --- a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/nested/Dump.java +++ b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/nested/Dump.java @@ -52,7 +52,7 @@ import javax.sql.DataSource; //import org.eclipse.jetty.http.HttpHeaders; //import org.eclipse.jetty.util.StringUtil; //import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; +//import org.eclipse.jetty.util.log.Logger; @@ -62,7 +62,7 @@ import org.eclipse.jetty.util.log.Logger; */ public class Dump extends HttpServlet { - private static final Logger LOG = Log.getLogger(Dump.class); + //private static final Logger LOG = Log.getLogger(Dump.class); boolean fixed; diff --git a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java new file mode 100644 index 00000000000..4bf16ee2b4d --- /dev/null +++ b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java @@ -0,0 +1,48 @@ +// ======================================================================== +// Copyright (c) 2010-2011 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.osgi.servletbridge; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.equinox.servletbridge.BridgeServlet; + +/** + * Override the BridgeServlet to report on whether equinox is actually started or not + * in case it is started asynchroneously. + * + * @author hmalphettes + */ +public class BridgeServletExtended extends BridgeServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (FrameworkLauncherExtended.ASYNCH_START_IN_PROGRESS != null + && req.getMethod().equals("GET")) { + if (FrameworkLauncherExtended.ASYNCH_START_IN_PROGRESS) { + resp.getWriter().append("Equinox is currently starting...\n"); + return; + } else if (FrameworkLauncherExtended.ASYNCH_START_FAILURE != null) { + resp.getWriter().append("Equinox failed to start:\n"); + FrameworkLauncherExtended.ASYNCH_START_FAILURE.printStackTrace(resp.getWriter()); + return; + } + } + super.service(req, resp); + } + +} diff --git a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/FrameworkLauncherExtended.java b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/FrameworkLauncherExtended.java index 52a926d384b..a0f2021e92f 100644 --- a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/FrameworkLauncherExtended.java +++ b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/FrameworkLauncherExtended.java @@ -49,6 +49,44 @@ public class FrameworkLauncherExtended extends FrameworkLauncher private boolean deployedInPlace = false; private URL resourceBaseAsURL = null; + + protected static Boolean ASYNCH_START_IN_PROGRESS; + protected static Throwable ASYNCH_START_FAILURE = null; + + /** + * If the start is asynch we do it in a different thread and return immediately. + */ + @Override + public synchronized void start() { + if (ASYNCH_START_IN_PROGRESS == null && "true".equals(super.config.getInitParameter("asyncStart"))) { + final ClassLoader webappCl = Thread.currentThread().getContextClassLoader(); + Thread th = new Thread() { + public void run() { + Thread.currentThread().setContextClassLoader(webappCl); + System.out.println("Jetty-Nested: Starting equinox asynchroneously."); + FrameworkLauncherExtended.this.start(); + System.out.println("Jetty-Nested: Finished starting equinox asynchroneously."); + } + }; + ASYNCH_START_IN_PROGRESS = true; + try { + th.start(); + } catch (Throwable t) { + ASYNCH_START_FAILURE = t; + if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } else { + throw new RuntimeException("Equinox failed to start", t); + } + } finally { + ASYNCH_START_IN_PROGRESS = false; + } + } else { + System.out.println("Jetty-Nested: Starting equinox synchroneously."); + super.start(); + System.out.println("Jetty-Nested: Finished starting equinox synchroneously."); + } + } /** * try to find the resource base for this webapp by looking for the launcher initialization file. @@ -248,7 +286,10 @@ public class FrameworkLauncherExtended extends FrameworkLauncher String logback = System.getProperty("logback.configurationFile"); if (logback == null) { - File etcLogback = new File(jettyHome,"etc/logback.xml"); + File etcLogback = new File(jettyHome,"etc/logback-nested.xml"); + if (!etcLogback.exists()) { + etcLogback = new File(jettyHome,"etc/logback.xml"); + } if (etcLogback.exists()) { System.setProperty("logback.configurationFile",etcLogback.getAbsolutePath()); diff --git a/jetty-osgi/jetty-osgi-servletbridge/src/main/webapp/WEB-INF/web.xml b/jetty-osgi/jetty-osgi-servletbridge/src/main/webapp/WEB-INF/web.xml index c538c7c9bba..c61dee582d1 100644 --- a/jetty-osgi/jetty-osgi-servletbridge/src/main/webapp/WEB-INF/web.xml +++ b/jetty-osgi/jetty-osgi-servletbridge/src/main/webapp/WEB-INF/web.xml @@ -5,13 +5,19 @@ proxy Transparent Proxy Servlet and Equinox Framework Controller Transparent Proxy Servlet and Equinox Framework Controller - org.eclipse.equinox.servletbridge.BridgeServlet + org.eclipse.jetty.osgi.servletbridge.BridgeServletExtended + When this parameter is defined, that equinox installation is launched in place. + The parameter can also be passed as a system property. --> osgi.install.area /WEB-INF/eclipse + + + + asyncStart + true commandline diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index 0f8a27f0cb1..ae30fdd313e 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml org.eclipse.jetty.osgi diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 817b0fd7ba5..f0ae38a1230 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty.osgi jetty-osgi-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/jetty-overlay-deployer/pom.xml b/jetty-overlay-deployer/pom.xml index d0324d74f53..75b8349308e 100644 --- a/jetty-overlay-deployer/pom.xml +++ b/jetty-overlay-deployer/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-overlay-deployer diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index 9cfd5f690c7..391eb9691c7 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-plus diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java index 35ed91e5d07..bf679cdc9b2 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java @@ -21,7 +21,7 @@ import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; -import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.server.Request; diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java index 1747463e850..77af2d1cce6 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java @@ -24,7 +24,7 @@ import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java index 5b1e7e92c39..5f7401724af 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java @@ -35,7 +35,7 @@ import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.plus.jaas.callback.ObjectCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java index 67a207042ff..8258d68aa72 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java @@ -13,146 +13,114 @@ package org.eclipse.jetty.plus.jaas.spi; -import java.io.File; -import java.io.FileInputStream; +import java.security.Principal; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.StringTokenizer; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.security.PropertyUserStore; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * PropertyFileLoginModule - * - * + * + * */ public class PropertyFileLoginModule extends AbstractLoginModule { + public static final String DEFAULT_FILENAME = "realm.properties"; + private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); - public static final String DEFAULT_FILENAME = "realm.properties"; - public static final Map> fileMap = new HashMap>(); - - private String propertyFileName; - - + private static Map _propertyUserStores = new HashMap(); - /** + private int _refreshInterval = 0; + private String _filename = DEFAULT_FILENAME; + + /** * Read contents of the configured property file. * - * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) + * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, + * java.util.Map) * @param subject * @param callbackHandler * @param sharedState * @param options */ - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - super.initialize(subject, callbackHandler, sharedState, options); - loadProperties((String)options.get("file")); + super.initialize(subject,callbackHandler,sharedState,options); + setupPropertyUserStore(options); } - - - - public void loadProperties (String filename) - { - File propsFile; - - if (filename == null) - { - propsFile = new File(System.getProperty("user.dir"), DEFAULT_FILENAME); - //look for a file called realm.properties in the current directory - //if that fails, look for a file called realm.properties in $jetty.home/etc - if (!propsFile.exists()) - propsFile = new File(System.getProperty("jetty.home"), DEFAULT_FILENAME); - } - else - { - propsFile = new File(filename); - } - - //give up, can't find a property file to load - if (!propsFile.exists()) - { - LOG.warn("No property file found"); - throw new IllegalStateException ("No property file specified in login module configuration file"); - } - - - - try - { - this.propertyFileName = propsFile.getCanonicalPath(); - if (fileMap.get(propertyFileName) != null) - { - if (LOG.isDebugEnabled()) {LOG.debug("Properties file "+propertyFileName+" already in cache, skipping load");} - return; - } - - Map userInfoMap = new HashMap(); - Properties props = new Properties(); - props.load(new FileInputStream(propsFile)); - Iterator> iter = props.entrySet().iterator(); - while(iter.hasNext()) - { - - Map.Entry entry = iter.next(); - String username=entry.getKey().toString().trim(); - String credentials=entry.getValue().toString().trim(); - String roles=null; - int c=credentials.indexOf(','); - if (c>0) - { - roles=credentials.substring(c+1).trim(); - credentials=credentials.substring(0,c).trim(); - } - if (username!=null && username.length()>0 && - credentials!=null && credentials.length()>0) - { - ArrayList roleList = new ArrayList(); - if(roles!=null && roles.length()>0) - { - StringTokenizer tok = new StringTokenizer(roles,", "); - - while (tok.hasMoreTokens()) - roleList.add(tok.nextToken()); - } - - userInfoMap.put(username, (new UserInfo(username, Credential.getCredential(credentials.toString()), roleList))); - } - } - - fileMap.put(propertyFileName, userInfoMap); - } - catch (Exception e) + private void setupPropertyUserStore(Map options) + { + if (_propertyUserStores.get(_filename) == null) { - LOG.warn("Error loading properties from file", e); - throw new RuntimeException(e); + parseConfig(options); + + PropertyUserStore _propertyUserStore = new PropertyUserStore(); + _propertyUserStore.setConfig(_filename); + _propertyUserStore.setRefreshInterval(_refreshInterval); + LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval); + + try + { + _propertyUserStore.start(); + } + catch (Exception e) + { + LOG.warn("Exception while starting propertyUserStore: ",e); + } + + _propertyUserStores.put(_filename,_propertyUserStore); } } - /** - * Don't implement this as we want to pre-fetch all of the - * users. - * @param username + private void parseConfig(Map options) + { + _filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME; + String refreshIntervalString = (String)options.get("refreshInterval"); + _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString); + } + + /** + * Don't implement this as we want to pre-fetch all of the users. + * + * @param userName * @throws Exception */ - public UserInfo getUserInfo (String username) throws Exception + public UserInfo getUserInfo(String userName) throws Exception { - Map userInfoMap = (Map)fileMap.get(propertyFileName); - if (userInfoMap == null) + PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename); + if (propertyUserStore == null) + throw new IllegalStateException("PropertyUserStore should never be null here!"); + + UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName); + if(userIdentity==null) return null; - return (UserInfo)userInfoMap.get(username); + + Set principals = userIdentity.getSubject().getPrincipals(); + + List roles = new ArrayList(); + + for ( Principal principal : principals ) + { + roles.add( principal.getName() ); + } + + Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next(); + LOG.debug("Found: " + userName + " in PropertyUserStore"); + return new UserInfo(userName, credential, roles); } } diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java index 01e9e6c8355..861acb20fef 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java @@ -16,7 +16,7 @@ package org.eclipse.jetty.plus.jaas.spi; import java.util.ArrayList; import java.util.List; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; /** * UserInfo diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java index 48a38bebf42..fc269ccfab2 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java @@ -27,7 +27,6 @@ import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.sql.DataSource; -import org.eclipse.jetty.http.security.Password; import org.eclipse.jetty.plus.jndi.NamingEntryUtil; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.MappedLoginService; @@ -35,6 +34,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Password; /** diff --git a/jetty-policy/pom.xml b/jetty-policy/pom.xml index 6b590cdc192..64548024678 100644 --- a/jetty-policy/pom.xml +++ b/jetty-policy/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT org.eclipse.jetty jetty-policy diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java index a89b422bea4..b8d7a719ed4 100644 --- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java +++ b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java @@ -21,6 +21,7 @@ import java.security.KeyStore; import org.eclipse.jetty.policy.PolicyContext; import org.eclipse.jetty.policy.PolicyException; +import org.eclipse.jetty.util.resource.Resource; public class KeystoreEntry extends AbstractEntry { @@ -49,7 +50,8 @@ public class KeystoreEntry extends AbstractEntry keystore = KeyStore.getInstance( type ); URL keyStoreLocation = new URL ( url ); - InputStream istream = keyStoreLocation.openStream(); + Resource r = Resource.newResource(keyStoreLocation); + InputStream istream = r.getInputStream(); keystore.load( istream, null ); diff --git a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java b/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java index ba5c2de8de7..06008534282 100644 --- a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java +++ b/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java @@ -45,7 +45,7 @@ public class PolicyMonitorTest { Thread.sleep(100); } - + Assert.assertEquals(1,count.get()); monitor.stop(); } @@ -73,17 +73,14 @@ public class PolicyMonitorTest monitor.setScanInterval(1); monitor.start(); - - while (! monitor.isInitialized() ) - { - Thread.sleep(100); - } - + monitor.waitForScan(); + monitor.waitForScan(); + File permFile =new File(MavenTestingUtils.getTargetDir(), "test-classes/monitor-test-2/global-all-permission.policy"); - // Wait so that time is definitely different - Thread.sleep(10); + // Wait so that time is definitely different + monitor.waitForScan(); permFile.setLastModified(System.currentTimeMillis()); monitor.waitForScan(); diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index dd9fb10475c..cdfe6d1c5b0 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-rewrite @@ -74,6 +74,11 @@ jetty-server ${project.version} + + org.eclipse.jetty + jetty-client + ${project.version} + javax.servlet servlet-api diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java new file mode 100644 index 00000000000..8e63b8c5a74 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java @@ -0,0 +1,497 @@ +package org.eclipse.jetty.rewrite.handler; + +//======================================================================== +//Copyright (c) 2006-2009 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. +//======================================================================== + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.util.Enumeration; +import java.util.HashSet; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/** + * This rule allows the user to configure a particular rewrite rule that will proxy out + * to a configured location. This rule uses the jetty http client. + * + * Rule rule = new ProxyRule(); + * rule.setPattern("/foo/*"); + * rule.setProxyTo("http://url.com"); + * + * see api for other configuration options which influence the configuration of the jetty + * client instance + * + */ +public class ProxyRule extends PatternRule +{ + private static final Logger _log = Log.getLogger(ProxyRule.class); + + private HttpClient _client; + private String _hostHeader; + private String _proxyTo; + + private int _connectorType = 2; + private String _maxThreads; + private String _maxConnections; + private String _timeout; + private String _idleTimeout; + private String _requestHeaderSize; + private String _requestBufferSize; + private String _responseHeaderSize; + private String _responseBufferSize; + + private HashSet _DontProxyHeaders = new HashSet(); + { + _DontProxyHeaders.add("proxy-connection"); + _DontProxyHeaders.add("connection"); + _DontProxyHeaders.add("keep-alive"); + _DontProxyHeaders.add("transfer-encoding"); + _DontProxyHeaders.add("te"); + _DontProxyHeaders.add("trailer"); + _DontProxyHeaders.add("proxy-authorization"); + _DontProxyHeaders.add("proxy-authenticate"); + _DontProxyHeaders.add("upgrade"); + } + + /* ------------------------------------------------------------ */ + public ProxyRule() + { + _handling = true; + _terminating = true; + } + + /* ------------------------------------------------------------ */ + private void initializeClient() throws Exception + { + _client = new HttpClient(); + _client.setConnectorType(_connectorType); + + if ( _maxThreads != null ) + { + _client.setThreadPool(new QueuedThreadPool(Integer.parseInt(_maxThreads))); + } + else + { + _client.setThreadPool(new QueuedThreadPool()); + } + + if ( _maxConnections != null ) + { + _client.setMaxConnectionsPerAddress(Integer.parseInt(_maxConnections)); + } + + if ( _timeout != null ) + { + _client.setTimeout(Long.parseLong(_timeout)); + } + + if ( _idleTimeout != null ) + { + _client.setIdleTimeout(Long.parseLong(_idleTimeout)); + } + + if ( _requestBufferSize != null ) + { + _client.setRequestBufferSize(Integer.parseInt(_requestBufferSize)); + } + + if ( _requestHeaderSize != null ) + { + _client.setRequestHeaderSize(Integer.parseInt(_requestHeaderSize)); + } + + if ( _responseBufferSize != null ) + { + _client.setResponseBufferSize(Integer.parseInt(_responseBufferSize)); + } + + if ( _responseHeaderSize != null ) + { + _client.setResponseHeaderSize(Integer.parseInt(_responseHeaderSize)); + } + + _client.start(); + } + + /* ------------------------------------------------------------ */ + private HttpURI proxyHttpURI(String uri) throws MalformedURLException + { + return new HttpURI(_proxyTo + uri); + } + + /* ------------------------------------------------------------ */ + @Override + protected String apply(String target, HttpServletRequest request, final HttpServletResponse response) throws IOException + { + synchronized (this) + { + if (_client == null) + { + try + { + initializeClient(); + } + catch (Exception e) + { + throw new IOException("Unable to proxy: " + e.getMessage()); + } + } + } + + final int debug = _log.isDebugEnabled()?request.hashCode():0; + + final InputStream in = request.getInputStream(); + final OutputStream out = response.getOutputStream(); + + HttpURI url = createUrl(request,debug); + + if (url == null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return target; + } + + HttpExchange exchange = new HttpExchange() + { + @Override + protected void onRequestCommitted() throws IOException + { + } + + @Override + protected void onRequestComplete() throws IOException + { + } + + @Override + protected void onResponseComplete() throws IOException + { + if (debug != 0) + _log.debug(debug + " complete"); + } + + @Override + protected void onResponseContent(Buffer content) throws IOException + { + if (debug != 0) + _log.debug(debug + " content" + content.length()); + content.writeTo(out); + } + + @Override + protected void onResponseHeaderComplete() throws IOException + { + } + + @SuppressWarnings("deprecation") + @Override + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (debug != 0) + _log.debug(debug + " " + version + " " + status + " " + reason); + + if (reason != null && reason.length() > 0) + response.setStatus(status,reason.toString()); + else + response.setStatus(status); + } + + @Override + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + String s = name.toString().toLowerCase(); + if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value))) + { + if (debug != 0) + _log.debug(debug + " " + name + ": " + value); + + response.addHeader(name.toString(),value.toString()); + } + else if (debug != 0) + _log.debug(debug + " " + name + "! " + value); + } + + @Override + protected void onConnectionFailed(Throwable ex) + { + _log.warn(ex.toString()); + _log.debug(ex); + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @Override + protected void onException(Throwable ex) + { + if (ex instanceof EofException) + { + _log.ignore(ex); + return; + } + _log.warn(ex.toString()); + _log.debug(ex); + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @Override + protected void onExpire() + { + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); + } + } + + }; + + exchange.setMethod(request.getMethod()); + exchange.setURL(url.toString()); + exchange.setVersion(request.getProtocol()); + + if (debug != 0) + { + _log.debug("{} {} {} {}", debug ,request.getMethod(), url, request.getProtocol()); + } + + boolean hasContent = createHeaders(request,debug,exchange); + + if (hasContent) + { + exchange.setRequestContentSource(in); + } + + /* + * we need to set the timeout on the exchange to take into account the timeout of the HttpClient and the HttpExchange + */ + long ctimeout = (_client.getTimeout() > exchange.getTimeout())?_client.getTimeout():exchange.getTimeout(); + exchange.setTimeout(ctimeout); + + _client.send(exchange); + + try + { + exchange.waitForDone(); + } + catch (InterruptedException e) + { + _log.info("Exception while waiting for response on proxied request", e); + } + return target; + } + + /* ------------------------------------------------------------ */ + private HttpURI createUrl(HttpServletRequest request, final int debug) throws MalformedURLException + { + String uri = request.getRequestURI(); + + if (request.getQueryString() != null) + { + uri += "?" + request.getQueryString(); + } + + uri = PathMap.pathInfo(_pattern,uri); + + if(uri==null) + { + uri = "/"; + } + + HttpURI url = proxyHttpURI(uri); + + if (debug != 0) + { + _log.debug(debug + " proxy " + uri + "-->" + url); + } + + return url; + } + + /* ------------------------------------------------------------ */ + private boolean createHeaders(final HttpServletRequest request, final int debug, HttpExchange exchange) + { + // check connection header + String connectionHdr = request.getHeader("Connection"); + if (connectionHdr != null) + { + connectionHdr = connectionHdr.toLowerCase(); + if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0) + { + connectionHdr = null; + } + } + + // force host + if (_hostHeader != null) + { + exchange.setRequestHeader("Host",_hostHeader); + } + + // copy headers + boolean xForwardedFor = false; + boolean hasContent = false; + long contentLength = -1; + Enumeration enm = request.getHeaderNames(); + while (enm.hasMoreElements()) + { + // TODO could be better than this! + String hdr = (String)enm.nextElement(); + String lhdr = hdr.toLowerCase(); + + if (_DontProxyHeaders.contains(lhdr)) + continue; + if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0) + continue; + if (_hostHeader != null && "host".equals(lhdr)) + continue; + + if ("content-type".equals(lhdr)) + hasContent = true; + else if ("content-length".equals(lhdr)) + { + contentLength = request.getContentLength(); + exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength)); + if (contentLength > 0) + hasContent = true; + } + else if ("x-forwarded-for".equals(lhdr)) + xForwardedFor = true; + + Enumeration vals = request.getHeaders(hdr); + while (vals.hasMoreElements()) + { + String val = (String)vals.nextElement(); + if (val != null) + { + if (debug != 0) + _log.debug("{} {} {}",debug,hdr,val); + + exchange.setRequestHeader(hdr,val); + } + } + } + + // Proxy headers + exchange.setRequestHeader("Via","1.1 (jetty)"); + if (!xForwardedFor) + { + exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr()); + exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme()); + exchange.addRequestHeader("X-Forwarded-Host",request.getServerName()); + exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName()); + } + return hasContent; + } + + /* ------------------------------------------------------------ */ + public void setProxyTo(String proxyTo) + { + this._proxyTo = proxyTo; + } + + /* ------------------------------------------------------------ */ + public void setMaxThreads(String maxThreads) + { + this._maxThreads = maxThreads; + } + + /* ------------------------------------------------------------ */ + public void setMaxConnections(String maxConnections) + { + _maxConnections = maxConnections; + } + + /* ------------------------------------------------------------ */ + public void setTimeout(String timeout) + { + _timeout = timeout; + } + + /* ------------------------------------------------------------ */ + public void setIdleTimeout(String idleTimeout) + { + _idleTimeout = idleTimeout; + } + + /* ------------------------------------------------------------ */ + public void setRequestHeaderSize(String requestHeaderSize) + { + _requestHeaderSize = requestHeaderSize; + } + + /* ------------------------------------------------------------ */ + public void setRequestBufferSize(String requestBufferSize) + { + _requestBufferSize = requestBufferSize; + } + + /* ------------------------------------------------------------ */ + public void setResponseHeaderSize(String responseHeaderSize) + { + _responseHeaderSize = responseHeaderSize; + } + + /* ------------------------------------------------------------ */ + public void setResponseBufferSize(String responseBufferSize) + { + _responseBufferSize = responseBufferSize; + } + + /* ------------------------------------------------------------ */ + public void addDontProxyHeaders(String dontProxyHeader) + { + _DontProxyHeaders.add(dontProxyHeader); + } + + /* ------------------------------------------------------------ */ + /** + * CONNECTOR_SOCKET = 0; + * CONNECTOR_SELECT_CHANNEL = 2; (default) + * + * @param connectorType + */ + public void setConnectorType( int connectorType ) + { + _connectorType = connectorType; + } + + public String getHostHeader() + { + return _hostHeader; + } + + public void setHostHeader(String hostHeader) + { + _hostHeader = hostHeader; + } + +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java index 98b9d0631a2..e563abd72f4 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java @@ -5,11 +5,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.rewrite.handler; @@ -19,25 +19,24 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; /* ------------------------------------------------------------ */ /** *

    Rewrite handler is responsible for managing the rules. Its capabilities - * is not only limited for URL rewrites such as RewritePatternRule or RewriteRegexRule. - * There is also handling for cookies, headers, redirection, setting status or error codes - * whenever the rule finds a match. - * - *

    The rules can be matched by the either: pattern matching of PathMap - * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or certain conditions set + * is not only limited for URL rewrites such as RewritePatternRule or RewriteRegexRule. + * There is also handling for cookies, headers, redirection, setting status or error codes + * whenever the rule finds a match. + * + *

    The rules can be matched by the either: pattern matching of PathMap + * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or certain conditions set * (eg {@link MsieSslRule} - the requests must be in SSL mode). - * - *

    The rules can be grouped into rule containers (class {@link RuleContainer}), and will only + * + *

    The rules can be grouped into rule containers (class {@link RuleContainer}), and will only * be applied if the request matches the conditions for their container * (e.g., by virtual host name) - * + * *

    The list of predefined rules is: *

      *
    • {@link CookiePatternRule} - adds a new cookie in response.
    • @@ -46,28 +45,36 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; *
    • {@link ResponsePatternRule} - sets the status/error codes.
    • *
    • {@link RewritePatternRule} - rewrites the requested URI.
    • *
    • {@link RewriteRegexRule} - rewrites the requested URI using regular expression for pattern matching.
    • + *
    • {@link ProxyRule} - proxies the requested URI to the host defined in proxyTo.
    • *
    • {@link MsieSslRule} - disables the keep alive on SSL for IE5 and IE6.
    • *
    • {@link LegacyRule} - the old version of rewrite.
    • *
    • {@link ForwardedSchemeHeaderRule} - set the scheme according to the headers present.
    • *
    • {@link VirtualHostRuleContainer} - checks whether the request matches one of a set of virtual host names.
    • *
    * - * + * * Here is a typical jetty.xml configuration would be:
    - * 
    + *
      *   <Set name="handler">
      *     <New id="Handlers" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
      *       <Set name="rules">
      *         <Array type="org.eclipse.jetty.rewrite.handler.Rule">
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
      *               <Set name="pattern">/*</Set>
      *               <Set name="replacement">/test</Set>
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
    + *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.ProxyRule">
    + *               <Set name="pattern">/*</Set>
    + *               <Set name="proxyTo">http://webtide.com:8080</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item>
      *             <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
      *               <Set name="pattern">/session/</Set>
      *               <Set name="code">400</Set>
    @@ -75,7 +82,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
      *               <Set name="pattern">*.jsp</Set>
      *               <Set name="name">server</Set>
    @@ -83,7 +90,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
      *               <Set name="pattern">*.jsp</Set>
      *               <Set name="name">title</Set>
    @@ -91,28 +98,28 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="redirect" class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
      *               <Set name="pattern">/test/dispatch</Set>
      *               <Set name="location">http://jetty.eclipse.org</Set>
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="regexRewrite" class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
      *               <Set name="regex">/test-jaas/$</Set>
      *               <Set name="replacement">/demo</Set>
      *             </New>
      *           </Item>
    - *           
    - *           <Item> 
    + *
    + *           <Item>
      *             <New id="forwardedHttps" class="org.eclipse.jetty.rewrite.handler.ForwardedSchemeHeaderRule">
      *               <Set name="header">X-Forwarded-Scheme</Set>
      *               <Set name="headerValue">https</Set>
      *               <Set name="scheme">https</Set>
      *             </New>
      *           </Item>
    - *           
    + *
      *           <Item>
      *             <New id="virtualHost" class="org.eclipse.jetty.rewrite.handler.VirtualHostRuleContainer">
      *
    @@ -134,10 +141,10 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *                   </New>
      *                 </Arg>
      *               </Call>
    - *    
    + *
      *             </New>
      *           </      Item>
    - * 
    + *
      *         </Array>
      *       </Set>
      *
    @@ -162,13 +169,13 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *     </New>
      *   </Set>
      * 
    - * + * */ public class RewriteHandler extends HandlerWrapper { - + private RuleContainer _rules; - + /* ------------------------------------------------------------ */ public RewriteHandler() { @@ -179,7 +186,7 @@ public class RewriteHandler extends HandlerWrapper /** * To enable configuration from jetty.xml on rewriteRequestURI, rewritePathInfo and * originalPathAttribute - * + * * @param legacyRule old style rewrite rule */ @Deprecated @@ -201,7 +208,7 @@ public class RewriteHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** * Assigns the rules to process. - * @param rules an array of {@link Rule}. + * @param rules an array of {@link Rule}. */ public void setRules(Rule[] rules) { @@ -297,13 +304,13 @@ public class RewriteHandler extends HandlerWrapper public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (isStarted()) - { + { String returned = _rules.matchAndApply(target, request, response); target = (returned == null) ? target : returned; - + if (!baseRequest.isHandled()) super.handle(target, baseRequest, request, response); } } - + } diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java index 201d4fadab1..0321859b27b 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java @@ -18,8 +18,7 @@ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.PathMap; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.log.Log; @@ -224,7 +223,7 @@ public class RuleContainer extends Rule if (rule.isHandling()) { LOG.debug("handling {}",rule); - (request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest()).setHandled(true); + (request instanceof Request?(Request)request:AbstractHttpConnection.getCurrentConnection().getRequest()).setHandled(true); } if (rule.isTerminating()) diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java index 955391dbe52..16575c4dfca 100644 --- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java @@ -15,7 +15,7 @@ package org.eclipse.jetty.rewrite.handler; import org.eclipse.jetty.io.bio.StringEndPoint; import org.eclipse.jetty.server.BlockingHttpConnection; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -27,7 +27,7 @@ public abstract class AbstractRuleTestCase protected Server _server = new Server(); protected LocalConnector _connector; protected StringEndPoint _endpoint = new StringEndPoint(); - protected HttpConnection _connection; + protected AbstractHttpConnection _connection; protected Request _request; protected Response _response; protected boolean _isSecure = false; diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java new file mode 100644 index 00000000000..9cb9def596a --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java @@ -0,0 +1,132 @@ +package org.eclipse.jetty.rewrite.handler; + +//======================================================================== +//Copyright (c) 2006-2009 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. +//======================================================================== + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URLEncoder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.ContentExchange; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ProxyRuleTest +{ + private static ProxyRule _rule; + private static RewriteHandler _handler; + private static Server _proxyServer = new Server(); + private static Connector _proxyServerConnector = new SelectChannelConnector(); + private static Server _targetServer = new Server(); + private static Connector _targetServerConnector = new SelectChannelConnector(); + private static HttpClient _httpClient = new HttpClient(); + + @BeforeClass + public static void setupOnce() throws Exception + { + _targetServer.addConnector(_targetServerConnector); + _targetServer.setHandler(new AbstractHandler() + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String responseString = "uri: " + request.getRequestURI() + " some content"; + response.getOutputStream().write(responseString.getBytes()); + response.setStatus(201); + } + }); + _targetServer.start(); + + _rule = new ProxyRule(); + _rule.setPattern("/foo/*"); + _rule.setProxyTo("http://localhost:" + _targetServerConnector.getLocalPort()); + _handler = new RewriteHandler(); + _handler.setRewriteRequestURI(true); + _handler.setRules(new Rule[] { _rule }); + + _proxyServer.addConnector(_proxyServerConnector); + _proxyServer.setHandler(_handler); + _proxyServer.start(); + + _httpClient.start(); + } + + @AfterClass + public static void destroy() throws Exception + { + _httpClient.stop(); + _proxyServer.stop(); + _targetServer.stop(); + _rule = null; + } + + @Test + public void testProxy() throws Exception + { + + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals("uri: / some content",exchange.getResponseContent()); + assertEquals(201,exchange.getResponseStatus()); + } + + @Test + public void testProxyWithDeeperPath() throws Exception + { + + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo/bar/foobar?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals("uri: /bar/foobar some content",exchange.getResponseContent()); + assertEquals(201,exchange.getResponseStatus()); + } + + @Test + public void testProxyNoMatch() throws Exception + { + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foobar?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals(404,exchange.getResponseStatus()); + } +} diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml index 83f86b6d3e8..e97f217ff65 100644 --- a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml +++ b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml @@ -1,5 +1,5 @@ - + @@ -85,7 +85,7 @@ - + /* /test diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml index 7e285d6e9cf..397991f6299 100644 --- a/jetty-security/pom.xml +++ b/jetty-security/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-security diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java index 7d65c482484..0f255300c8b 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java @@ -20,9 +20,8 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.eclipse.jetty.server.Authentication; -import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Authentication.User; -import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.Server; /** * Authenticator Interface diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java index 378ff14d575..13361cf1e46 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintMapping.java @@ -13,7 +13,7 @@ package org.eclipse.jetty.security; -import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.util.security.Constraint; public class ConstraintMapping { 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 f8d92e8d279..44280872b43 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 @@ -25,14 +25,14 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import org.eclipse.jetty.http.PathMap; -import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.security.Constraint; /* ------------------------------------------------------------ */ /** @@ -351,7 +351,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr { return true; } - HttpConnection connection = HttpConnection.getCurrentConnection(); + AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); Connector connector = connection.getConnector(); if (dataConstraint == UserDataConstraint.Integral) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java index 0016be50b41..6c243a3a873 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.security; import javax.servlet.ServletContext; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.Authenticator.AuthConfiguration; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.security.authentication.ClientCertAuthenticator; @@ -23,6 +22,7 @@ import org.eclipse.jetty.security.authentication.DigestAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.security.authentication.SpnegoAuthenticator; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.security.Constraint; /* ------------------------------------------------------------ */ /** diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java index cf94075de0f..e9d2b9a7d37 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.security; -import java.io.Serializable; import java.security.Principal; import javax.security.auth.Subject; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java index 5e5a8445863..34f7a5cedef 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -13,50 +13,38 @@ package org.eclipse.jetty.security; -import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.PropertyUserStore.UserListener; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Scanner; -import org.eclipse.jetty.util.Scanner.BulkListener; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.Credential; /* ------------------------------------------------------------ */ /** * Properties User Realm. * - * An implementation of UserRealm that stores users and roles in-memory in - * HashMaps. + * An implementation of UserRealm that stores users and roles in-memory in HashMaps. *

    - * Typically these maps are populated by calling the load() method or passing a - * properties resource to the constructor. The format of the properties file is: + * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is: * *

      *  username: password [,rolename ...]
      * 
    * - * Passwords may be clear text, obfuscated or checksummed. The class - * com.eclipse.Util.Password should be used to generate obfuscated passwords or - * password checksums. + * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password + * checksums. * - * If DIGEST Authentication is used, the password must be in a recoverable - * format, either plain text or OBF:. + * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:. */ -public class HashLoginService extends MappedLoginService +public class HashLoginService extends MappedLoginService implements UserListener { private static final Logger LOG = Log.getLogger(HashLoginService.class); + private PropertyUserStore _propertyUserStore; private String _config; private Resource _configResource; private Scanner _scanner; @@ -72,14 +60,14 @@ public class HashLoginService extends MappedLoginService { setName(name); } - + /* ------------------------------------------------------------ */ public HashLoginService(String name, String config) { setName(name); setConfig(config); } - + /* ------------------------------------------------------------ */ public String getConfig() { @@ -89,7 +77,7 @@ public class HashLoginService extends MappedLoginService /* ------------------------------------------------------------ */ public void getConfig(String config) { - _config=config; + _config = config; } /* ------------------------------------------------------------ */ @@ -100,11 +88,10 @@ public class HashLoginService extends MappedLoginService /* ------------------------------------------------------------ */ /** - * Load realm users from properties file. The property file maps usernames - * to password specs followed by an optional comma separated list of role - * names. + * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names. * - * @param config Filename or url of user properties file. + * @param config + * Filename or url of user properties file. */ public void setConfig(String config) { @@ -129,52 +116,14 @@ public class HashLoginService extends MappedLoginService { return null; } - + /* ------------------------------------------------------------ */ @Override public void loadUsers() throws IOException { - if (_config==null) - return; - _configResource = Resource.newResource(_config); - - if (LOG.isDebugEnabled()) LOG.debug("Load " + this + " from " + _config); - Properties properties = new Properties(); - properties.load(_configResource.getInputStream()); - Set known = new HashSet(); - - for (Map.Entry entry : properties.entrySet()) - { - String username = ((String) entry.getKey()).trim(); - String credentials = ((String) entry.getValue()).trim(); - String roles = null; - int c = credentials.indexOf(','); - if (c > 0) - { - roles = credentials.substring(c + 1).trim(); - credentials = credentials.substring(0, c).trim(); - } - - if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0) - { - String[] roleArray = IdentityService.NO_ROLES; - if (roles != null && roles.length() > 0) - roleArray = roles.split(","); - known.add(username); - putUser(username,Credential.getCredential(credentials),roleArray); - } - } - - Iterator users = _users.keySet().iterator(); - while(users.hasNext()) - { - String user=users.next(); - if (!known.contains(user)) - users.remove(); - } + // TODO: Consider refactoring MappedLoginService to not have to override with unused methods } - /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() @@ -183,54 +132,16 @@ public class HashLoginService extends MappedLoginService { super.doStart(); - if (getRefreshInterval() > 0) + if (_propertyUserStore == null) { - _scanner = new Scanner(); - _scanner.setScanInterval(getRefreshInterval()); - List dirList = new ArrayList(1); - dirList.add(_configResource.getFile()); - _scanner.setScanDirs(dirList); - _scanner.setFilenameFilter(new FilenameFilter() - { - public boolean accept(File dir, String name) - { - File f = new File(dir, name); - try - { - if (f.compareTo(_configResource.getFile()) == 0) return true; - } - catch (IOException e) - { - return false; - } - - return false; - } - - }); - _scanner.addListener(new BulkListener() - { - public void filesChanged(List filenames) throws Exception - { - if (filenames == null) return; - if (filenames.isEmpty()) return; - if (filenames.size() == 1) - { - Resource r = Resource.newResource(filenames.get(0)); - if (r.getFile().equals(_configResource.getFile())) - loadUsers(); - } - } - - public String toString() - { - return "HashLoginService$Scanner"; - } - - }); - _scanner.setReportExistingFilesOnStartup(false); - _scanner.setRecursive(false); - _scanner.start(); + if(LOG.isDebugEnabled()) + LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval); + + _propertyUserStore = new PropertyUserStore(); + _propertyUserStore.setRefreshInterval(_refreshInterval); + _propertyUserStore.setConfig(_config); + _propertyUserStore.registerUserListener(this); + _propertyUserStore.start(); } } @@ -241,9 +152,24 @@ public class HashLoginService extends MappedLoginService protected void doStop() throws Exception { super.doStop(); - if (_scanner != null) _scanner.stop(); + if (_scanner != null) + _scanner.stop(); _scanner = null; } + + /* ------------------------------------------------------------ */ + public void update(String userName, Credential credential, String[] roleArray) + { + if (LOG.isDebugEnabled()) + LOG.debug("update: " + userName + " Roles: " + roleArray.length); + putUser(userName,credential,roleArray); + } - + /* ------------------------------------------------------------ */ + public void remove(String userName) + { + if (LOG.isDebugEnabled()) + LOG.debug("remove: " + userName); + removeUser(userName); + } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java index 03a4786efbb..44e7ef078e1 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java @@ -23,12 +23,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; -import org.eclipse.jetty.http.security.Credential; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.Credential; /* ------------------------------------------------------------ */ /** @@ -137,7 +137,7 @@ public class JDBCLoginService extends MappedLoginService || _password == null || _cacheTime < 0) { - if (LOG.isDebugEnabled()) LOG.debug("UserRealm " + getName() + " has not been properly configured"); + LOG.warn("UserRealm " + getName() + " has not been properly configured"); } _cacheTime *= 1000; _lastHashPurge = 0; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java index 8808be26dae..5ca896fb154 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java @@ -23,11 +23,11 @@ import java.util.concurrent.ConcurrentMap; import javax.security.auth.Subject; -import org.eclipse.jetty.http.security.Credential; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Credential; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index 5e463165103..d3921bcab0a 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -3,7 +3,9 @@ package org.eclipse.jetty.security; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.security.Principal; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -11,13 +13,18 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import org.eclipse.jetty.http.security.Credential; +import javax.security.auth.Subject; + +import org.eclipse.jetty.security.MappedLoginService.KnownUser; +import org.eclipse.jetty.security.MappedLoginService.RolePrincipal; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.Scanner.BulkListener; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.Credential; /** * PropertyUserStore @@ -42,8 +49,10 @@ public class PropertyUserStore extends AbstractLifeCycle private Scanner _scanner; private int _refreshInterval = 0;// default is not to reload + private IdentityService _identityService = new DefaultIdentityService(); private boolean _firstLoad = true; // true if first load, false from that point on private final List _knownUsers = new ArrayList(); + private final Map _knownUserIdentities = new HashMap(); private List _listeners; /* ------------------------------------------------------------ */ @@ -57,6 +66,12 @@ public class PropertyUserStore extends AbstractLifeCycle { _config = config; } + + /* ------------------------------------------------------------ */ + public UserIdentity getUserIdentity(String userName) + { + return _knownUserIdentities.get(userName); + } /* ------------------------------------------------------------ */ /** @@ -119,9 +134,29 @@ public class PropertyUserStore extends AbstractLifeCycle { String[] roleArray = IdentityService.NO_ROLES; if (roles != null && roles.length() > 0) + { roleArray = roles.split(","); + } known.add(username); - notifyUpdate(username,Credential.getCredential(credentials),roleArray); + Credential credential = Credential.getCredential(credentials); + + Principal userPrincipal = new KnownUser(username,credential); + Subject subject = new Subject(); + subject.getPrincipals().add(userPrincipal); + subject.getPrivateCredentials().add(credential); + + if (roles != null) + { + for (String role : roleArray) + { + subject.getPrincipals().add(new RolePrincipal(role)); + } + } + + subject.setReadOnly(); + + _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray)); + notifyUpdate(username,credential,roleArray); } } @@ -138,6 +173,7 @@ public class PropertyUserStore extends AbstractLifeCycle String user = users.next(); if (!known.contains(user)) { + _knownUserIdentities.remove(user); notifyRemove(user); } } @@ -201,15 +237,17 @@ public class PropertyUserStore extends AbstractLifeCycle _scanner.addListener(new BulkListener() { - public void filesChanged(List filenames) throws Exception + public void filesChanged(List filenames) throws Exception { if (filenames == null) return; if (filenames.isEmpty()) return; - if (filenames.size() == 1 && filenames.get(0).equals(getConfigResource().getFile().getAbsolutePath())) + if (filenames.size() == 1) { - loadUsers(); + Resource r = Resource.newResource(filenames.get(0)); + if (r.getFile().equals(_configResource.getFile())) + loadUsers(); } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 5878381ec2e..6a4b837783f 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -318,11 +318,13 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti if (!_loginServiceShared && _loginService instanceof LifeCycle) ((LifeCycle)_loginService).start(); + System.err.println("authenticator="+_authenticator+" authenticatorFactory="+_authenticatorFactory+" identityService="+_identityService); if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null) { _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService); if (_authenticator!=null) _authMethod=_authenticator.getAuthMethod(); + System.err.println("Called auth factory, authenticator="+_authenticator); } if (_authenticator==null) @@ -477,7 +479,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti deferred.setIdentityService(_identityService); deferred.setLoginService(_loginService); baseRequest.setAuthentication(authentication); - +System.err.println("uri="+baseRequest.getUri()+" Auth is deferred"); try { handler.handle(pathInContext, baseRequest, request, response); @@ -487,7 +489,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti previousIdentity = deferred.getPreviousAssociation(); deferred.setIdentityService(null); } - + System.err.println("Securityhandler calling secureResponse, for Authentication.User"); Authentication auth=baseRequest.getAuthentication(); if (auth instanceof Authentication.User) { diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java index f4949b05d40..94321f03ae6 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java @@ -15,17 +15,16 @@ package org.eclipse.jetty.security; //You may elect to redistribute this code under either of these licenses. //======================================================================== -import java.util.Collections; import java.util.Properties; import javax.security.auth.Subject; -import org.eclipse.jetty.http.security.B64Code; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.B64Code; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; @@ -100,7 +99,7 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic _targetName = properties.getProperty("targetName"); - LOG.debug("\n\nTarget Name\n\n" + _targetName); + LOG.debug("Target Name {}", _targetName); super.doStart(); } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java index bfadf400317..a30f60996f5 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java @@ -2,7 +2,7 @@ package org.eclipse.jetty.security; import java.security.Principal; -import org.eclipse.jetty.http.security.B64Code; +import org.eclipse.jetty.util.security.B64Code; public class SpnegoUserPrincipal implements Principal { diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java index cef019b14fe..da55e961c47 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java @@ -13,9 +13,6 @@ package org.eclipse.jetty.security; -import java.io.Serializable; - -import org.eclipse.jetty.security.authentication.LoginAuthenticator; import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity.Scope; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java index e680644609f..7857b4d46ae 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java @@ -21,14 +21,14 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; -import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.Authentication.User; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.security.Constraint; /** * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ @@ -65,20 +65,28 @@ public class BasicAuthenticator extends LoginAuthenticator return _deferred; if (credentials != null) - { - credentials = credentials.substring(credentials.indexOf(' ')+1); - credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); - int i = credentials.indexOf(':'); - if (i>0) + { + int space=credentials.indexOf(' '); + if (space>0) { - String username = credentials.substring(0,i); - String password = credentials.substring(i+1); - - UserIdentity user = _loginService.login(username,password); - if (user!=null) + String method=credentials.substring(0,space); + if ("basic".equalsIgnoreCase(method)) { - renewSessionOnAuthentication(request,response); - return new UserAuthentication(getAuthMethod(),user); + credentials = credentials.substring(space+1); + credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); + int i = credentials.indexOf(':'); + if (i>0) + { + String username = credentials.substring(0,i); + String password = credentials.substring(i+1); + + UserIdentity user = _loginService.login(username,password); + if (user!=null) + { + renewSessionOnAuthentication(request,response); + return new UserAuthentication(getAuthMethod(),user); + } + } } } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java index 9efd783b8a0..25220375506 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java @@ -25,8 +25,6 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Password; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; @@ -35,6 +33,8 @@ import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.security.CertificateUtils; import org.eclipse.jetty.util.security.CertificateValidator; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; /** * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java index e1aabf587ce..297d5beeff0 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java @@ -167,7 +167,7 @@ public class DeferredAuthentication implements Authentication.Deferred * @param response * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)} */ - public boolean isDeferred(HttpServletResponse response) + public static boolean isDeferred(HttpServletResponse response) { return response==__deferredResponse; } @@ -175,7 +175,7 @@ public class DeferredAuthentication implements Authentication.Deferred /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ - static HttpServletResponse __deferredResponse = new HttpServletResponse() + final static HttpServletResponse __deferredResponse = new HttpServletResponse() { public void addCookie(Cookie cookie) { diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java index 65471e2881c..51833fad789 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java @@ -15,6 +15,12 @@ package org.eclipse.jetty.security.authentication; import java.io.IOException; import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -22,37 +28,48 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Credential; -import org.eclipse.jetty.security.Authenticator.AuthConfiguration; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.UserIdentity; -import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; /** * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ * - * The nonce max age can be set with the {@link SecurityHandler#setInitParameter(String, String)} + * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} * using the name "maxNonceAge" */ public class DigestAuthenticator extends LoginAuthenticator { private static final Logger LOG = Log.getLogger(DigestAuthenticator.class); + SecureRandom _random = new SecureRandom(); + private long _maxNonceAgeMs = 60*1000; + private ConcurrentMap _nonceCount = new ConcurrentHashMap(); + private Queue _nonceQueue = new ConcurrentLinkedQueue(); + private static class Nonce + { + final String _nonce; + final long _ts; + AtomicInteger _nc=new AtomicInteger(); + public Nonce(String nonce, long ts) + { + _nonce=nonce; + _ts=ts; + } + } - protected long _maxNonceAge = 0; - protected long _nonceSecret = this.hashCode() ^ System.currentTimeMillis(); - protected boolean _useStale = false; - + /* ------------------------------------------------------------ */ public DigestAuthenticator() { super(); @@ -69,19 +86,33 @@ public class DigestAuthenticator extends LoginAuthenticator String mna=configuration.getInitParameter("maxNonceAge"); if (mna!=null) - _maxNonceAge=Long.valueOf(mna); + { + synchronized (this) + { + _maxNonceAgeMs=Long.valueOf(mna); + } + } + } + + /* ------------------------------------------------------------ */ + public synchronized void setMaxNonceAge(long maxNonceAgeInMillis) + { + _maxNonceAgeMs = maxNonceAgeInMillis; } + /* ------------------------------------------------------------ */ public String getAuthMethod() { return Constraint.__DIGEST_AUTH; } - + + /* ------------------------------------------------------------ */ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException { return true; } + /* ------------------------------------------------------------ */ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException { if (!mandatory) @@ -96,7 +127,8 @@ public class DigestAuthenticator extends LoginAuthenticator boolean stale = false; if (credentials != null) { - if (LOG.isDebugEnabled()) LOG.debug("Credentials: " + credentials); + if (LOG.isDebugEnabled()) + LOG.debug("Credentials: " + credentials); QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false); final Digest digest = new Digest(request.getMethod()); String last = null; @@ -115,6 +147,7 @@ public class DigestAuthenticator extends LoginAuthenticator break; case ',': name = null; + break; case ' ': break; @@ -143,7 +176,7 @@ public class DigestAuthenticator extends LoginAuthenticator } } - int n = checkNonce(digest.nonce, (Request)request); + int n = checkNonce(digest,(Request)request); if (n > 0) { @@ -169,8 +202,8 @@ public class DigestAuthenticator extends LoginAuthenticator + domain + "\", nonce=\"" + newNonce((Request)request) - + "\", algorithm=MD5, qop=\"auth\"" - + (_useStale ? (" stale=" + stale) : "")); + + "\", algorithm=MD5, qop=\"auth\"," + + " stale=" + stale); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return Authentication.SEND_CONTINUE; @@ -185,87 +218,63 @@ public class DigestAuthenticator extends LoginAuthenticator } + /* ------------------------------------------------------------ */ public String newNonce(Request request) { - long ts=request.getTimeStamp(); - long sk = _nonceSecret; - - byte[] nounce = new byte[24]; - for (int i = 0; i < 8; i++) + Nonce nonce; + + do { - nounce[i] = (byte) (ts & 0xff); - ts = ts >> 8; - nounce[8 + i] = (byte) (sk & 0xff); - sk = sk >> 8; - } + byte[] nounce = new byte[24]; + _random.nextBytes(nounce); - byte[] hash = null; - try - { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.reset(); - md.update(nounce, 0, 16); - hash = md.digest(); + nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp()); } - catch (Exception e) - { - LOG.warn(e); - } - - for (int i = 0; i < hash.length; i++) - { - nounce[8 + i] = hash[i]; - if (i == 23) break; - } - - return new String(B64Code.encode(nounce)); + while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null); + _nonceQueue.add(nonce); + + return nonce._nonce; } /** - * @param nonce nonce to check + * @param nstring nonce to check * @param request * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce */ /* ------------------------------------------------------------ */ - private int checkNonce(String nonce, Request request) + private int checkNonce(Digest digest, Request request) { + // firstly let's expire old nonces + long expired; + synchronized (this) + { + expired = request.getTimeStamp()-_maxNonceAgeMs; + } + + Nonce nonce=_nonceQueue.peek(); + while (nonce!=null && nonce._ts> 8; - ts = (ts << 8) + (0xff & (long) n[7 - i]); - } - - long age = request.getTimeStamp() - ts; - if (LOG.isDebugEnabled()) LOG.debug("age=" + age); - - byte[] hash = null; - try - { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.reset(); - md.update(n2, 0, 16); - hash = md.digest(); - } - catch (Exception e) - { - LOG.warn(e); - } - - for (int i = 0; i < 16; i++) - if (n[i + 8] != hash[i]) return -1; - - if (_maxNonceAge > 0 && (age < 0 || age > _maxNonceAge)) return 0; // stale - + nonce = _nonceCount.get(digest.nonce); + if (nonce==null) + return 0; + + long count = Long.parseLong(digest.nc,16); + if (count>Integer.MAX_VALUE) + return 0; + int old=nonce._nc.get(); + while (!nonce._nc.compareAndSet(old,(int)count)) + old=nonce._nc.get(); + if (count<=old) + return -1; + return 1; } catch (Exception e) @@ -275,18 +284,21 @@ public class DigestAuthenticator extends LoginAuthenticator return -1; } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ private static class Digest extends Credential { private static final long serialVersionUID = -2484639019549527724L; - String method = null; - String username = null; - String realm = null; - String nonce = null; - String nc = null; - String cnonce = null; - String qop = null; - String uri = null; - String response = null; + final String method; + String username = ""; + String realm = ""; + String nonce = ""; + String nc = ""; + String cnonce = ""; + String qop = ""; + String uri = ""; + String response = ""; /* ------------------------------------------------------------ */ Digest(String m) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java index 83696e68da6..c21768fde17 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java @@ -30,20 +30,19 @@ import javax.servlet.http.HttpSession; import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Authentication; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.UserIdentity; -import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Constraint; /** * FORM Authenticator. @@ -260,7 +259,7 @@ public class FormAuthenticator extends LoginAuthenticator // so restore method and parameters session.removeAttribute(__J_POST); - Request base_request = (req instanceof Request)?(Request)req:HttpConnection.getCurrentConnection().getRequest(); + Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest(); base_request.setMethod(HttpMethods.POST); base_request.setParameters(j_post); } @@ -290,7 +289,7 @@ public class FormAuthenticator extends LoginAuthenticator if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(req.getContentType()) && HttpMethods.POST.equals(request.getMethod())) { - Request base_request = (req instanceof Request)?(Request)req:HttpConnection.getCurrentConnection().getRequest(); + Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest(); base_request.extractParameters(); session.setAttribute(__J_POST, new MultiMap(base_request.getParameters())); } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java index 98cb1968b6d..c730a9bab5c 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpSession; import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; -import org.eclipse.jetty.server.SessionManager; public abstract class LoginAuthenticator implements Authenticator { diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java index 0e28f3d4193..e63e597ccae 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java @@ -16,20 +16,16 @@ package org.eclipse.jetty.security.authentication; import java.io.IOException; import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.io.Serializable; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionEvent; -import org.eclipse.jetty.security.Authenticator; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.SecurityHandler; -import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity.Scope; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java index 8ed9a790a0f..9df7448468a 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java @@ -23,7 +23,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeaders; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; @@ -31,6 +30,7 @@ import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.security.Constraint; public class SpnegoAuthenticator extends LoginAuthenticator { 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 9a40357de9e..47190675b63 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 @@ -27,8 +27,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Password; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.server.Connector; @@ -41,7 +39,8 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.B64Code; -import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -201,13 +200,13 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -218,20 +217,20 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403 ")); assertTrue(response.indexOf("!role") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -490,18 +489,18 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403")); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -512,20 +511,20 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403 ")); assertTrue(response.indexOf("!role") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -776,7 +775,7 @@ public class ConstraintTest assertTrue(response.startsWith("HTTP/1.1 200 OK")); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 500 ")); @@ -789,7 +788,7 @@ public class ConstraintTest _server.start(); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); } @@ -809,13 +808,13 @@ public class ConstraintTest assertTrue(response.indexOf("user=null") > 0); response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+ - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertTrue(response.indexOf("user=null") > 0); response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+ - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertTrue(response.indexOf("user=admin") > 0); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java index dde1df50bc1..2e24c178c4d 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java @@ -9,7 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger; import junit.framework.Assert; -import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.util.security.Credential; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -78,6 +78,9 @@ public class PropertyUserStoreTest store.start(); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("tom")); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("dick")); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("harry")); Assert.assertEquals(3,userCount.get()); } @@ -117,7 +120,11 @@ public class PropertyUserStoreTest long start = System.currentTimeMillis(); while (userCount.get() < 4 && (System.currentTimeMillis() - start) < 10000) + { Thread.sleep(10); + } + + Assert.assertNotNull("Failed to retrieve UserIdentity from PropertyUserStore directly", store.getUserIdentity("skip")); Assert.assertEquals(4,userCount.get()); Assert.assertTrue(users.contains("skip")); diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index 2b51c386c9d..8e71f15966d 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-server @@ -107,5 +107,11 @@ ${project.version} true
    + + org.mockito + mockito-core + 1.8.5 + test +
    diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index 199d0e18e26..6f349ca4f8b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -48,8 +48,8 @@ import org.eclipse.jetty.util.thread.ThreadPool; *
  • Base acceptor thread
  • *
  • Optional reverse proxy headers checking
  • * - * - * + * + * */ public abstract class AbstractConnector extends HttpBuffers implements Connector, Dumpable { @@ -181,7 +181,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector *

    * Previously, Jetty supported separate idle timeouts and IO operation timeouts, however the expense of changing the value of soTimeout was significant, so * these timeouts were merged. With the advent of NIO, it may be possible to again differentiate these values (if there is demand). - * + * * @param maxIdleTime * The maxIdleTime to set. */ @@ -358,7 +358,11 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ public void join() throws InterruptedException { - Thread[] threads = _acceptorThread; + Thread[] threads; + synchronized(this) + { + threads= _acceptorThread; + } if (threads != null) for (int i = 0; i < threads.length; i++) if (threads[i] != null) @@ -410,13 +414,13 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector request.setScheme(HttpSchemes.HTTPS); } } - + // Retrieving headers from the request String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader()); String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader()); String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader()); String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader()); - + if (_hostHeader != null) { // Update host header @@ -458,7 +462,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector request.setRemoteHost(inetAddress == null?forwardedFor:inetAddress.getHostName()); } - + if (forwardedProto != null) { request.setScheme(forwardedProto); @@ -611,7 +615,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ /** * Is reverse proxy handling on? - * + * * @return true if this connector is checking the x-forwarded-for/host/server headers */ public boolean isForwarded() @@ -623,7 +627,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /** * Set reverse proxy handling. If set to true, then the X-Forwarded headers (or the headers set in their place) are looked for to set the request protocol, * host, server and client ip. - * + * * @param check * true if this connector is checking the x-forwarded-for/host/server headers * @set {@link #setForwardedForHeader(String)} @@ -634,7 +638,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector public void setForwarded(boolean check) { if (check) - LOG.debug(this + " is forwarded"); + LOG.debug("{} is forwarded",this); _forwarded = check; } @@ -648,7 +652,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /** * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. * This value is only used if {@link #isForwarded()} is true. - * + * * @param hostHeader * The value of the host header to force. */ @@ -659,7 +663,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ /* - * + * * @see #setForwarded(boolean) */ public String getForwardedHostHeader() @@ -722,7 +726,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ /** * Get the forwardedProtoHeader. - * + * * @return the forwardedProtoHeader (default X-Forwarded-For) * @see #setForwarded(boolean) */ @@ -734,7 +738,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ /** * Set the forwardedProtoHeader. - * + * * @param forwardedProtoHeader * the forwardedProtoHeader to set (default X-Forwarded-For) * @see #setForwarded(boolean) @@ -845,10 +849,6 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector // Connector has been stopped LOG.ignore(x); } - catch (ThreadDeath e) - { - throw e; - } catch (Throwable e) { LOG.warn(e); @@ -1001,7 +1001,8 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector if (on && _statsStartedAt.get() != -1) return; - LOG.debug("Statistics on = " + on + " for " + this); + if (LOG.isDebugEnabled()) + LOG.debug("Statistics on = " + on + " for " + this); statsReset(); _statsStartedAt.set(on?System.currentTimeMillis():-1); @@ -1039,19 +1040,19 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /* ------------------------------------------------------------ */ protected void connectionUpgraded(Connection oldConnection, Connection newConnection) { - _requestStats.set((oldConnection instanceof HttpConnection)?((HttpConnection)oldConnection).getRequests():0); + _requestStats.set((oldConnection instanceof AbstractHttpConnection)?((AbstractHttpConnection)oldConnection).getRequests():0); } /* ------------------------------------------------------------ */ protected void connectionClosed(Connection connection) { - connection.closed(); + connection.onClose(); if (_statsStartedAt.get() == -1) return; long duration = System.currentTimeMillis() - connection.getTimeStamp(); - int requests = (connection instanceof HttpConnection)?((HttpConnection)connection).getRequests():0; + int requests = (connection instanceof AbstractHttpConnection)?((AbstractHttpConnection)connection).getRequests():0; _requestStats.set(requests); _connectionStats.decrement(); _connectionDurationStats.set(duration); @@ -1070,7 +1071,7 @@ public abstract class AbstractConnector extends HttpBuffers implements Connector /** * Set the priority offset of the acceptor threads. The priority is adjusted by this amount (default 0) to either favour the acceptance of new threads and * newly active connections or to favour the handling of already dispatched connections. - * + * * @param offset * the amount to alter the priority of the acceptor threads. */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java similarity index 89% rename from jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java rename to jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java index c77f76af97a..c4e9e3e266d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java @@ -1,5 +1,5 @@ // ======================================================================== -// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// Copyright (c) 2004-2011 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 @@ -16,13 +16,11 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; - import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.continuation.ContinuationThrowable; -import org.eclipse.jetty.http.AbstractGenerator; import org.eclipse.jetty.http.EncodedHttpURI; import org.eclipse.jetty.http.Generator; import org.eclipse.jetty.http.HttpBuffers; @@ -40,9 +38,9 @@ import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.Parser; import org.eclipse.jetty.io.AbstractConnection; -import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; @@ -56,7 +54,6 @@ import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.thread.Timeout; /** *

    A HttpConnection represents the connection of a HTTP client to the server @@ -89,12 +86,12 @@ import org.eclipse.jetty.util.thread.Timeout; *

    * */ -public abstract class HttpConnection extends AbstractConnection +public abstract class AbstractHttpConnection extends AbstractConnection { - private static final Logger LOG = Log.getLogger(HttpConnection.class); + private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class); private static final int UNKNOWN = -2; - private static final ThreadLocal __currentConnection = new ThreadLocal(); + private static final ThreadLocal __currentConnection = new ThreadLocal(); private int _requests; @@ -128,13 +125,13 @@ public abstract class HttpConnection extends AbstractConnection private boolean _delayedHandling=false; /* ------------------------------------------------------------ */ - public static HttpConnection getCurrentConnection() + public static AbstractHttpConnection getCurrentConnection() { return __currentConnection.get(); } /* ------------------------------------------------------------ */ - protected static void setCurrentConnection(HttpConnection connection) + protected static void setCurrentConnection(AbstractHttpConnection connection) { __currentConnection.set(connection); } @@ -143,13 +140,13 @@ public abstract class HttpConnection extends AbstractConnection /** Constructor * */ - public HttpConnection(Connector connector, EndPoint endpoint, Server server) + public AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server) { super(endpoint); _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _connector = connector; HttpBuffers ab = (HttpBuffers)_connector; - _parser = new HttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); + _parser = newHttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); _requestFields = new HttpFields(); _responseFields = new HttpFields(server.getMaxCookieVersion()); _request = new Request(this); @@ -160,11 +157,11 @@ public abstract class HttpConnection extends AbstractConnection } /* ------------------------------------------------------------ */ - protected HttpConnection(Connector connector, EndPoint endpoint, Server server, + protected AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request) { super(endpoint); - + _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _connector = connector; _parser = parser; @@ -177,6 +174,11 @@ public abstract class HttpConnection extends AbstractConnection _server = server; } + protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endpoint, HttpParser.EventHandler requestHandler) + { + return new HttpParser(requestBuffers, endpoint, requestHandler); + } + /* ------------------------------------------------------------ */ /** * @return the parser used by this connection @@ -194,13 +196,13 @@ public abstract class HttpConnection extends AbstractConnection { return _requests; } - + /* ------------------------------------------------------------ */ public Server getServer() { return _server; } - + /* ------------------------------------------------------------ */ /** * @return Returns the associatedObject. @@ -327,7 +329,7 @@ public abstract class HttpConnection extends AbstractConnection } if (_in == null) - _in = new HttpInput(((HttpParser)_parser),_connector.getMaxIdleTime()); + _in = new HttpInput(AbstractHttpConnection.this); return _in; } @@ -353,7 +355,27 @@ public abstract class HttpConnection extends AbstractConnection if (_writer==null) { _writer=new OutputWriter(); - _printWriter=new UncheckedPrintWriter(_writer); + if (_server.isUncheckedPrintWriter()) + _printWriter=new UncheckedPrintWriter(_writer); + else + _printWriter = new PrintWriter(_writer) + { + public void close() + { + synchronized (lock) + { + try + { + out.close(); + } + catch (IOException e) + { + setError(); + } + } + } + }; + } _writer.setCharacterEncoding(encoding); return _printWriter; @@ -366,30 +388,16 @@ public abstract class HttpConnection extends AbstractConnection } /* ------------------------------------------------------------ */ - public void scheduleTimeout(Timeout.Task task, long timeoutMs) + public void reset() { - throw new UnsupportedOperationException(); - } - - /* ------------------------------------------------------------ */ - public void cancelTimeout(Timeout.Task task) - { - throw new UnsupportedOperationException(); - } - - /* ------------------------------------------------------------ */ - public void reset(boolean returnBuffers) - { - _parser.reset(); - if (returnBuffers) - _parser.returnBuffers(); + _parser.reset(); + _parser.returnBuffers(); // TODO maybe only on unhandle _requestFields.clear(); _request.recycle(); - - _generator.reset(returnBuffers); // TODO maybe only release when low on resources + _generator.reset(); + _generator.returnBuffers();// TODO maybe only on unhandle _responseFields.clear(); _response.recycle(); - _uri.clear(); } @@ -453,29 +461,26 @@ public abstract class HttpConnection extends AbstractConnection catch (EofException e) { LOG.debug(e); - _request.setHandled(true); error=true; + _request.setHandled(true); } catch (RuntimeIOException e) { LOG.debug(e); - _request.setHandled(true); error=true; + _request.setHandled(true); } catch (HttpException e) { LOG.debug(e); + error=true; _request.setHandled(true); _response.sendError(e.getStatus(), e.getReason()); - error=true; } catch (Throwable e) { - if (e instanceof ThreadDeath) - throw (ThreadDeath)e; - - error=true; LOG.warn(String.valueOf(_uri),e); + error=true; _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); } @@ -509,7 +514,12 @@ public abstract class HttpConnection extends AbstractConnection if(_endp.isOpen()) { if (error) + { _endp.shutdownOutput(); + _generator.setPersistent(false); + if (!_generator.isComplete()) + _response.complete(); + } else { if (!_response.isCommitted() && !_request.isHandled()) @@ -552,10 +562,10 @@ public abstract class HttpConnection extends AbstractConnection } catch(RuntimeException e) { - LOG.warn("header full: "+e); + LOG.warn("header full: " + e); _response.reset(); - _generator.reset(true); + _generator.reset(); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); _generator.completeHeader(_responseFields,Generator.LAST); _generator.complete(); @@ -587,7 +597,7 @@ public abstract class HttpConnection extends AbstractConnection LOG.debug(e); _response.reset(); - _generator.reset(true); + _generator.reset(); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); _generator.completeHeader(_responseFields,Generator.LAST); _generator.complete(); @@ -654,11 +664,11 @@ public abstract class HttpConnection extends AbstractConnection } /* ------------------------------------------------------------ */ - public void closed() + public void onClose() { LOG.debug("closed {}",this); } - + /* ------------------------------------------------------------ */ public boolean isExpecting100Continues() { @@ -671,6 +681,26 @@ public abstract class HttpConnection extends AbstractConnection return _expect102Processing; } + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime()) + return _connector.getLowResourceMaxIdleTime(); + if (_endp.getMaxIdleTime()>0) + return _endp.getMaxIdleTime(); + return _connector.getMaxIdleTime(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s,g=%s,p=%s,r=%d", + super.toString(), + _generator, + _parser, + _requests); + } + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -687,7 +717,7 @@ public abstract class HttpConnection extends AbstractConnection public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException { uri=uri.asImmutableBuffer(); - + _host = false; _expect = false; _expect100Continue=false; @@ -710,7 +740,8 @@ public abstract class HttpConnection extends AbstractConnection case HttpMethods.HEAD_ORDINAL: _head=true; - // fall through + _uri.parse(uri.array(), uri.getIndex(), uri.length()); + break; default: _uri.parse(uri.array(), uri.getIndex(), uri.length()); @@ -802,46 +833,6 @@ public abstract class HttpConnection extends AbstractConnection value = MimeTypes.CACHE.lookup(value); _charset=MimeTypes.getCharsetFromContentType(value); break; - - case HttpHeaders.CONNECTION_ORDINAL: - //looks rather clumsy, but the idea is to optimize for a single valued header - switch(HttpHeaderValues.CACHE.getOrdinal(value)) - { - case -1: - { - String[] values = value.toString().split(","); - for (int i=0;values!=null && i _continuationListeners; /* ------------------------------------------------------------ */ @@ -90,7 +90,7 @@ public class AsyncContinuation implements AsyncContext, Continuation } /* ------------------------------------------------------------ */ - protected void setConnection(final HttpConnection connection) + protected void setConnection(final AbstractHttpConnection connection) { synchronized(this) { @@ -183,6 +183,45 @@ public class AsyncContinuation implements AsyncContext, Continuation } } } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#isSuspended() + */ + public boolean isSuspending() + { + synchronized(this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __ASYNCWAIT: + return true; + + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isDispatchable() + { + synchronized(this) + { + switch(_state) + { + case __REDISPATCH: + case __REDISPATCHED: + case __REDISPATCHING: + case __COMPLETING: + return true; + + default: + return false; + } + } + } /* ------------------------------------------------------------ */ @Override @@ -539,7 +578,7 @@ public class AsyncContinuation implements AsyncContext, Continuation EndPoint endp=_connection.getEndPoint(); if (!endp.isBlocking()) { - ((AsyncEndPoint)endp).dispatch(); + ((AsyncEndPoint)endp).asyncDispatch(); } } @@ -576,7 +615,7 @@ public class AsyncContinuation implements AsyncContext, Continuation } else { - _connection.scheduleTimeout(_event,_timeoutMs); + ((AsyncEndPoint)endp).scheduleTimeout(_event,_timeoutMs); } } } @@ -597,7 +636,9 @@ public class AsyncContinuation implements AsyncContext, Continuation { final AsyncEventState event=_event; if (event!=null) - _connection.cancelTimeout(event); + { + ((AsyncEndPoint)endp).cancelTimeout(event); + } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java index 70175378cdf..031ad11e8d0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java @@ -1,3 +1,16 @@ +// ======================================================================== +// Copyright (c) 2006-2011 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; @@ -7,52 +20,72 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class AsyncHttpConnection extends HttpConnection + +/* ------------------------------------------------------------ */ +/** Asychronous Server HTTP connection + * + */ +public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection { + private final static int NO_PROGRESS_INFO = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_INFO",100); + private final static int NO_PROGRESS_CLOSE = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_CLOSE",200); + private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class); + private int _total_no_progress; + private final AsyncEndPoint _asyncEndp; public AsyncHttpConnection(Connector connector, EndPoint endpoint, Server server) { super(connector,endpoint,server); + _asyncEndp=(AsyncEndPoint)endpoint; } public Connection handle() throws IOException { Connection connection = this; - - // Loop while more in buffer + boolean some_progress=false; + boolean progress=true; + try { setCurrentConnection(this); - - boolean progress=true; - boolean more_in_buffer =false; - while (_endp.isOpen() && (more_in_buffer || progress)) + // don't check for idle while dispatched (unless blocking IO is done). + _asyncEndp.setCheckForIdle(false); + + + // While progress and the connection has not changed + while (progress && connection==this) { progress=false; try { - LOG.debug("async request",_request); - // Handle resumed request - if (_request._async.isAsync() && !_request._async.isComplete()) - handleRequest(); - + if (_request._async.isAsync()) + { + if (_request._async.isDispatchable()) + handleRequest(); + } // else Parse more input - else if (!_parser.isComplete() && _parser.parseAvailable()>0) + else if (!_parser.isComplete() && _parser.parseAvailable()) progress=true; // Generate more output - if (_generator.isCommitted() && !_generator.isComplete() && _generator.flushBuffer()>0) + if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown()) + if (_generator.flushBuffer()>0) + progress=true; + + // Flush output + _endp.flush(); + + // Has any IO been done by the endpoint itself since last loop + if (_asyncEndp.hasProgressed()) progress=true; - - // Flush output from buffering endpoint - if (_endp.isBufferingOutput()) - _endp.flush(); } catch (HttpException e) { @@ -62,67 +95,89 @@ public class AsyncHttpConnection extends HttpConnection LOG.debug("fields="+_requestFields); LOG.debug(e); } + progress=true; _generator.sendError(e.getStatus(), e.getReason(), null, true); - _parser.reset(); - _endp.close(); } finally { - // Do we need to complete a half close? - if (_endp.isInputShutdown() && (_parser.isIdle() || _parser.isComplete())) - { - LOG.debug("complete half close {}",this); - more_in_buffer=false; - _endp.close(); - reset(true); - } - - // else Is this request/response round complete? - else if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput()) + some_progress|=progress; + // Is this request/response round complete and are fully flushed? + if (_parser.isComplete() && _generator.isComplete()) { + // Reset the parser/generator + progress=true; + // look for a switched connection instance? if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) { Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"); if (switched!=null) - { - _parser.reset(); - _generator.reset(true); - return switched; - } + connection=switched; } - - // Reset the parser/generator - // keep the buffers as we will cycle - progress=true; - reset(false); - more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); - } - // else Are we suspended? - else if (_request.isAsyncStarted()) - { - LOG.debug("suspended {}",this); - more_in_buffer=false; - progress=false; + reset(); + + // TODO Is this still required? + if (!_generator.isPersistent() && !_endp.isOutputShutdown()) + { + LOG.warn("Safety net oshut!!! IF YOU SEE THIS, PLEASE RAISE BUGZILLA"); + _endp.shutdownOutput(); + } + } + else if (_request.getAsyncContinuation().isAsyncStarted()) + { + // The request is suspended, so even though progress has been made, break the while loop + LOG.debug("suspended {}",this); + // TODO: breaking inside finally blocks is bad: rethink how we should exit from here + break; } - else - more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); } } } finally { setCurrentConnection(null); - _parser.returnBuffers(); - - // Are we write blocked - if (_generator.isCommitted() && !_generator.isComplete()) - ((AsyncEndPoint)_endp).scheduleWrite(); - else + + // If we are not suspended + if (!_request.getAsyncContinuation().isAsyncStarted()) + { + // return buffers + _parser.returnBuffers(); _generator.returnBuffers(); + } + + if (_request.getAsyncContinuation().isComplete() || _request.getAsyncContinuation().isInitial()) + { + _asyncEndp.setCheckForIdle(true); + } + + // Safety net to catch spinning + if (some_progress) + _total_no_progress=0; + else + { + _total_no_progress++; + if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE)) + LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this); + if (NO_PROGRESS_CLOSE>0 && _total_no_progress==NO_PROGRESS_CLOSE) + { + LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this); + if (_endp instanceof SelectChannelEndPoint) + ((SelectChannelEndPoint)_endp).getChannel().close(); + } + } } return connection; } + public void onInputShutdown() throws IOException + { + // If we don't have a committed response and we are not suspended + if (_generator.isIdle() && !_request.getAsyncContinuation().isSuspended()) + { + // then no more can happen, so close. + _endp.close(); + } + } + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java index a6e040f77b2..6b5c8284da4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java @@ -1,3 +1,15 @@ +// ======================================================================== +// Copyright (c) 2006-2011 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; @@ -6,66 +18,62 @@ import org.eclipse.jetty.http.Generator; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.Parser; -import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class BlockingHttpConnection extends HttpConnection + +/* ------------------------------------------------------------ */ +/** Blocking Server HTTP Connection + */ +public class BlockingHttpConnection extends AbstractHttpConnection { private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class); - private volatile boolean _handling; - public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server) { super(connector,endpoint,server); } - public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request) { super(connector,endpoint,server,parser,generator,request); } + @Override + protected void handleRequest() throws IOException + { + super.handleRequest(); + } public Connection handle() throws IOException { Connection connection = this; - // Loop while more in buffer - boolean more_in_buffer =true; // assume true until proven otherwise - try { setCurrentConnection(this); - while (more_in_buffer && _endp.isOpen()) + // do while the endpoint is open + // AND the connection has not changed + while (_endp.isOpen() && connection==this) { try { // If we are not ended then parse available - if (!_parser.isComplete()) + if (!_parser.isComplete() && !_endp.isInputShutdown()) _parser.parseAvailable(); // Do we have more generating to do? // Loop here because some writes may take multiple steps and // we need to flush them all before potentially blocking in the // next loop. - while (_generator.isCommitted() && !_generator.isComplete()) - { - long written=_generator.flushBuffer(); - if (written<=0) - break; - if (_endp.isBufferingOutput()) - _endp.flush(); - } + if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown()) + _generator.flushBuffer(); // Flush buffers - if (_endp.isBufferingOutput()) - _endp.flush(); - + _endp.flush(); } catch (HttpException e) { @@ -76,65 +84,49 @@ public class BlockingHttpConnection extends HttpConnection LOG.debug(e); } _generator.sendError(e.getStatus(), e.getReason(), null, true); - _parser.reset(); - _endp.close(); + _endp.shutdownOutput(); } finally { - more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); - - // Is this request/response round complete? - if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput()) + // Is this request/response round complete and are fully flushed? + if (_parser.isComplete() && _generator.isComplete()) { + // Reset the parser/generator + reset(); + // look for a switched connection instance? - Connection switched=(_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) - ?(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"):null; - - // have we switched? - if (switched!=null) + if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) { - _parser.reset(); - _generator.reset(true); - connection=switched; + Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"); + if (switched!=null) + connection=switched; } - else - { - // No switch, so cleanup and reset - if (!_generator.isPersistent() || _endp.isInputShutdown()) - { - _parser.reset(); - more_in_buffer=false; - _endp.close(); - } - if (more_in_buffer) - { - reset(false); - more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); - } - else - reset(true); + // TODO Is this required? + if (!_generator.isPersistent() && !_endp.isOutputShutdown()) + { + LOG.warn("Safety net oshut!!! Please open a bugzilla"); + _endp.shutdownOutput(); } } - else if (_parser.isIdle() && _endp.isInputShutdown()) + + // If we don't have a committed response and we are not suspended + if (_endp.isInputShutdown() && _generator.isIdle() && !_request.getAsyncContinuation().isSuspended()) { - more_in_buffer=false; + // then no more can happen, so close. _endp.close(); } - - if (_request.isAsyncStarted()) - throw new IllegalStateException(); } } + + return connection; } finally { - _parser.returnBuffers(); setCurrentConnection(null); - _handling=false; + _parser.returnBuffers(); + _generator.returnBuffers(); } - return connection; } - } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java index ddad84d6aa3..95c30b15139 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java @@ -135,14 +135,14 @@ public interface Connector extends LifeCycle /* ------------------------------------------------------------ */ /** * @return The port to use when redirecting a request if a data constraint of integral is - * required. See {@link org.eclipse.jetty.http.security.Constraint#getDataConstraint()} + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} */ int getIntegralPort(); /* ------------------------------------------------------------ */ /** * @return The schema to use when redirecting a request if a data constraint of integral is - * required. See {@link org.eclipse.jetty.http.security.Constraint#getDataConstraint()} + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} */ String getIntegralScheme(); @@ -156,7 +156,7 @@ public interface Connector extends LifeCycle /* ------------------------------------------------------------ */ /** * @return The port to use when redirecting a request if a data constraint of confidential is - * required. See {@link org.eclipse.jetty.http.security.Constraint#getDataConstraint()} + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} */ int getConfidentialPort(); @@ -164,7 +164,7 @@ public interface Connector extends LifeCycle /* ------------------------------------------------------------ */ /** * @return The schema to use when redirecting a request if a data constraint of confidential is - * required. See {@link org.eclipse.jetty.http.security.Constraint#getDataConstraint()} + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} */ String getConfidentialScheme(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index 355fc46adaa..99c9ec33127 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -130,7 +130,7 @@ public class Dispatcher implements RequestDispatcher */ public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { - Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed if (!(request instanceof HttpServletRequest)) @@ -211,7 +211,7 @@ public class Dispatcher implements RequestDispatcher */ protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException { - Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); Response base_response=baseRequest.getResponse(); response.resetBuffer(); base_response.fwdReset(); @@ -222,6 +222,7 @@ public class Dispatcher implements RequestDispatcher if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); + final boolean old_handled=baseRequest.isHandled(); final String old_uri=baseRequest.getRequestURI(); final String old_context_path=baseRequest.getContextPath(); final String old_servlet_path=baseRequest.getServletPath(); @@ -233,6 +234,7 @@ public class Dispatcher implements RequestDispatcher try { + baseRequest.setHandled(false); baseRequest.setDispatcherType(dispatch); if (_named!=null) @@ -279,6 +281,8 @@ public class Dispatcher implements RequestDispatcher baseRequest.setRequestURI(_uri); baseRequest.setContextPath(_contextHandler.getContextPath()); + baseRequest.setServletPath(null); + baseRequest.setPathInfo(_uri); baseRequest.setAttributes(attr); _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); @@ -303,6 +307,7 @@ public class Dispatcher implements RequestDispatcher } finally { + baseRequest.setHandled(old_handled); baseRequest.setRequestURI(old_uri); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java index 519c8730c54..cf16c0b14a7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java @@ -47,10 +47,10 @@ public interface Handler extends LifeCycle, Destroyable * @param target The target of the request - either a URI or a name. * @param baseRequest The original unwrapped request object. * @param request The request either as the {@link Request} - * object or a wrapper of that request. The {@link HttpConnection#getCurrentConnection()} + * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} * method can be used access the Request object if required. * @param response The response as the {@link Response} - * object or a wrapper of that request. The {@link HttpConnection#getCurrentConnection()} + * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} * method can be used access the Response object if required. * @throws IOException * @throws ServletException diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index f0bbc9aec8b..abaec917d7e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -22,14 +22,14 @@ import org.eclipse.jetty.io.Buffer; public class HttpInput extends ServletInputStream { + protected final AbstractHttpConnection _connection; protected final HttpParser _parser; - protected final long _maxIdleTime; /* ------------------------------------------------------------ */ - public HttpInput(HttpParser parser, long maxIdleTime) + public HttpInput(AbstractHttpConnection connection) { - _parser=parser; - _maxIdleTime=maxIdleTime; + _connection=connection; + _parser=(HttpParser)connection.getParser(); } /* ------------------------------------------------------------ */ @@ -40,7 +40,7 @@ public class HttpInput extends ServletInputStream public int read() throws IOException { int c=-1; - Buffer content=_parser.blockForContent(_maxIdleTime); + Buffer content=_parser.blockForContent(_connection.getMaxIdleTime()); if (content!=null) c= 0xff & content.get(); return c; @@ -54,7 +54,7 @@ public class HttpInput extends ServletInputStream public int read(byte[] b, int off, int len) throws IOException { int l=-1; - Buffer content=_parser.blockForContent(_maxIdleTime); + Buffer content=_parser.blockForContent(_connection.getMaxIdleTime()); if (content!=null) l= content.get(b, off, len); return l; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 5fd7d91c522..7f5d2628b4d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -36,8 +36,8 @@ import org.eclipse.jetty.util.ByteArrayOutputStream2; */ public class HttpOutput extends ServletOutputStream { + protected final AbstractHttpConnection _connection; protected final AbstractGenerator _generator; - protected final long _maxIdleTime; private boolean _closed; // These are held here for reuse by Writer @@ -45,15 +45,20 @@ public class HttpOutput extends ServletOutputStream Writer _converter; char[] _chars; ByteArrayOutputStream2 _bytes; - /* ------------------------------------------------------------ */ - public HttpOutput(AbstractGenerator generator, long maxIdleTime) + public HttpOutput(AbstractHttpConnection connection) { - _generator=generator; - _maxIdleTime=maxIdleTime; + _connection=connection; + _generator=(AbstractGenerator)connection.getGenerator(); } + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _connection.getMaxIdleTime(); + } + /* ------------------------------------------------------------ */ public boolean isWritten() { @@ -86,7 +91,7 @@ public class HttpOutput extends ServletOutputStream @Override public void flush() throws IOException { - _generator.flush(_maxIdleTime); + _generator.flush(getMaxIdleTime()); } /* ------------------------------------------------------------ */ @@ -121,7 +126,7 @@ public class HttpOutput extends ServletOutputStream // Block until we can add _content. while (_generator.isBufferFull()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) @@ -151,7 +156,7 @@ public class HttpOutput extends ServletOutputStream // Block until we can add _content. while (_generator.isBufferFull()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) @@ -174,7 +179,7 @@ public class HttpOutput extends ServletOutputStream // Block until our buffer is free while (buffer.length() > 0 && _generator.isOpen()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java index 401f924ec78..149b1bfbc19 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -23,9 +23,12 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class LocalConnector extends AbstractConnector { + private static final Logger LOG = Log.getLogger(LocalConnector.class); private final BlockingQueue _requests = new LinkedBlockingQueue(); public LocalConnector() @@ -107,13 +110,14 @@ public class LocalConnector extends AbstractConnector @Override public void setConnection(Connection connection) { - connectionUpgraded(getConnection(),connection); + if (getConnection()!=null && connection!=getConnection()) + connectionUpgraded(getConnection(),connection); super.setConnection(connection); } }; endPoint.setGrowOutput(true); - HttpConnection connection = new BlockingHttpConnection(LocalConnector.this, endPoint, getServer()); + AbstractHttpConnection connection = new BlockingHttpConnection(LocalConnector.this, endPoint, getServer()); endPoint.setConnection(connection); connectionOpened(connection); @@ -135,8 +139,14 @@ public class LocalConnector extends AbstractConnector } } } + catch (IOException x) + { + LOG.debug(x); + leaveOpen = false; + } catch (Exception x) { + LOG.warn(x); leaveOpen = false; } finally 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 4382d46cc8d..7a8e8215ca4 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 @@ -629,7 +629,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() */ @Override - protected void doStart() throws Exception + protected synchronized void doStart() throws Exception { if (_logDateFormat != 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 3a239ff6011..4fc8c5934d5 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 @@ -96,11 +96,19 @@ import org.eclipse.jetty.util.log.Logger; * against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result. * * - * A request instance is created for each {@link HttpConnection} accepted by the server + * A request instance is created for each {@link AbstractHttpConnection} accepted by the server * and recycled for each HTTP request received via that connection. An effort is made * to avoid reparsing headers and cookies that are likely to be the same for * requests from the same connection. * + *

    + * The form content that a request can process is limited to protect from Denial of Service + * attacks. The size in bytes is limited by {@link ContextHandler#getMaxFormContentSize()} or if there is no + * context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server} attribute. + * The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no + * context then the "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute. + * + * */ public class Request implements HttpServletRequest { @@ -116,7 +124,7 @@ public class Request implements HttpServletRequest if (request instanceof Request) return (Request) request; - return HttpConnection.getCurrentConnection().getRequest(); + return AbstractHttpConnection.getCurrentConnection().getRequest(); } protected final AsyncContinuation _async = new AsyncContinuation(); private boolean _asyncSupported=true; @@ -124,7 +132,7 @@ public class Request implements HttpServletRequest private Authentication _authentication; private MultiMap _baseParameters; private String _characterEncoding; - protected HttpConnection _connection; + protected AbstractHttpConnection _connection; private ContextHandler.Context _context; private boolean _newContext; private String _contextPath; @@ -170,7 +178,7 @@ public class Request implements HttpServletRequest } /* ------------------------------------------------------------ */ - public Request(HttpConnection connection) + public Request(AbstractHttpConnection connection) { setConnection(connection); } @@ -186,79 +194,36 @@ public class Request implements HttpServletRequest /* ------------------------------------------------------------ */ /** - * Extract Paramters from query string and/or form _content. + * Extract Parameters from query string and/or form _content. */ public void extractParameters() { if (_baseParameters == null) _baseParameters = new MultiMap(16); - + if (_paramsExtracted) { if (_parameters==null) _parameters=_baseParameters; return; } - + _paramsExtracted = true; - // Handle query string - if (_uri!=null && _uri.hasQuery()) + try { - if (_queryEncoding==null) - _uri.decodeQueryTo(_baseParameters); - else + // Handle query string + if (_uri!=null && _uri.hasQuery()) { - try - { - _uri.decodeQueryTo(_baseParameters,_queryEncoding); - } - catch (UnsupportedEncodingException e) - { - if (LOG.isDebugEnabled()) - LOG.warn(e); - else - LOG.warn(e.toString()); - } - } - } - - // handle any _content. - String encoding = getCharacterEncoding(); - String content_type = getContentType(); - if (content_type != null && content_type.length() > 0) - { - content_type = HttpFields.valueParameters(content_type, null); - - if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState==__NONE && - (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod()))) - { - int content_length = getContentLength(); - if (content_length != 0) + if (_queryEncoding==null) + _uri.decodeQueryTo(_baseParameters); + else { try { - int maxFormContentSize=-1; - - if (_context!=null) - maxFormContentSize=_context.getContextHandler().getMaxFormContentSize(); - else - { - Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); - if (size!=null) - maxFormContentSize =size.intValue(); - } - - if (content_length>maxFormContentSize && maxFormContentSize > 0) - { - throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); - } - InputStream in = getInputStream(); - - // Add form params to query params - UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1); + _uri.decodeQueryTo(_baseParameters,_queryEncoding); } - catch (IOException e) + catch (UnsupportedEncodingException e) { if (LOG.isDebugEnabled()) LOG.warn(e); @@ -267,29 +232,87 @@ public class Request implements HttpServletRequest } } } - } - - if (_parameters==null) - _parameters=_baseParameters; - else if (_parameters!=_baseParameters) - { - // Merge parameters (needed if parameters extracted after a forward). - Iterator iter = _baseParameters.entrySet().iterator(); - while (iter.hasNext()) + + // handle any _content. + String encoding = getCharacterEncoding(); + String content_type = getContentType(); + if (content_type != null && content_type.length() > 0) { - Map.Entry entry = (Map.Entry)iter.next(); - String name=(String)entry.getKey(); - Object values=entry.getValue(); - for (int i=0;imaxFormContentSize && maxFormContentSize > 0) + { + throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); + } + InputStream in = getInputStream(); + + // Add form params to query params + UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1,maxFormKeys); + } + catch (IOException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + } + } } - } + + if (_parameters==null) + _parameters=_baseParameters; + else if (_parameters!=_baseParameters) + { + // Merge parameters (needed if parameters extracted after a forward). + Iterator iter = _baseParameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + Object values=entry.getValue(); + for (int i=0;i=0) + { + http_only=true; + comment=comment.substring(i,i+HTTP_ONLY_COMMENT.length()).trim(); + if (comment.length()==0) + comment=null; + } + } _connection.getResponseFields().addSetCookie(cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), cookie.getMaxAge(), - cookie.getComment(), + comment, cookie.getSecure(), - false,//cookie.isHttpOnly(), + http_only,// || cookie.isHttpOnly(), cookie.getVersion()); } @@ -430,7 +451,7 @@ public class Response implements HttpServletResponse if (!canonical.equals(path)) { buf = _connection.getRequest().getRootURL(); - buf.append(canonical); + buf.append(URIUtil.encodePath(canonical)); if (uri.getQuery()!=null) { buf.append('?'); 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 10cfd69efa3..ccfd2cf4691 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 @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server; @@ -16,7 +16,6 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Enumeration; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -56,14 +55,14 @@ public class Server extends HandlerWrapper implements Attributes private static final String __version; static { - if (Server.class.getPackage()!=null && + if (Server.class.getPackage()!=null && "Eclipse.org - Jetty".equals(Server.class.getPackage().getImplementationVendor()) && Server.class.getPackage().getImplementationVersion()!=null) __version=Server.class.getPackage().getImplementationVersion(); else __version=System.getProperty("jetty.version","7.x.y-SNAPSHOT"); } - + private final Container _container=new Container(); private final AttributesMap _attributes = new AttributesMap(); private ThreadPool _threadPool; @@ -76,14 +75,15 @@ public class Server extends HandlerWrapper implements Attributes private int _maxCookieVersion=1; private boolean _dumpAfterStart=false; private boolean _dumpBeforeStop=false; - + private boolean _uncheckedPrintWriter=false; + /* ------------------------------------------------------------ */ public Server() { - setServer(this); + setServer(this); } - + /* ------------------------------------------------------------ */ /** Convenience constructor * Creates server and a {@link SelectChannelConnector} at the passed port. @@ -96,7 +96,7 @@ public class Server extends HandlerWrapper implements Attributes connector.setPort(port); setConnectors(new Connector[]{connector}); } - + /* ------------------------------------------------------------ */ /** Convenience constructor * Creates server and a {@link SelectChannelConnector} at the passed address. @@ -117,7 +117,7 @@ public class Server extends HandlerWrapper implements Attributes { return __version; } - + /* ------------------------------------------------------------ */ /** * @return Returns the container. @@ -132,7 +132,7 @@ public class Server extends HandlerWrapper implements Attributes { return _stopAtShutdown; } - + /* ------------------------------------------------------------ */ public void setStopAtShutdown(boolean stop) { @@ -142,7 +142,7 @@ public class Server extends HandlerWrapper implements Attributes else ShutdownThread.deregister(this); } - + /* ------------------------------------------------------------ */ /** * @return Returns the connectors. @@ -160,7 +160,7 @@ public class Server extends HandlerWrapper implements Attributes /* ------------------------------------------------------------ */ /** - * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to + * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to * remove a connector. * @param connector The connector to remove. */ @@ -180,7 +180,7 @@ public class Server extends HandlerWrapper implements Attributes for (int i=0;i0) { if (_connectors!=null) @@ -302,7 +302,7 @@ public class Server extends HandlerWrapper implements Attributes try{_connectors[i].close();}catch(Throwable e){mex.add(e);} } } - + Handler[] contexts = getChildHandlersByClass(Graceful.class); for (int c=0;c0;) @@ -320,7 +320,7 @@ public class Server extends HandlerWrapper implements Attributes } try {super.doStop(); } catch(Throwable e) { mex.add(e);} - + mex.ifExceptionThrow(); if (getStopAtShutdown()) @@ -333,12 +333,12 @@ public class Server extends HandlerWrapper implements Attributes * or after the entire request has been received (for short requests of known length), or * on the dispatch of an async request. */ - public void handle(HttpConnection connection) throws IOException, ServletException + public void handle(AbstractHttpConnection connection) throws IOException, ServletException { final String target=connection.getRequest().getPathInfo(); final Request request=connection.getRequest(); final Response response=connection.getResponse(); - + if (LOG.isDebugEnabled()) { LOG.debug("REQUEST "+target+" on "+connection); @@ -348,14 +348,14 @@ public class Server extends HandlerWrapper implements Attributes else handle(target, request, request, response); } - + /* ------------------------------------------------------------ */ /* Handle a request from a connection. * Called to handle a request on the connection when either the header has been received, * or after the entire request has been received (for short requests of known length), or * on the dispatch of an async request. */ - public void handleAsync(HttpConnection connection) throws IOException, ServletException + public void handleAsync(AbstractHttpConnection connection) throws IOException, ServletException { final AsyncContinuation async = connection.getRequest().getAsyncContinuation(); final AsyncContinuation.AsyncEventState state = async.getAsyncEventState(); @@ -377,7 +377,7 @@ public class Server extends HandlerWrapper implements Attributes baseRequest.setRequestURI(null); baseRequest.setPathInfo(baseRequest.getRequestURI()); if (uri.getQuery()!=null) - baseRequest.mergeQueryString(uri.getQuery()); + baseRequest.mergeQueryString(uri.getQuery()); } final String target=baseRequest.getPathInfo(); @@ -394,10 +394,10 @@ public class Server extends HandlerWrapper implements Attributes handle(target, baseRequest, request, response); } - - + + /* ------------------------------------------------------------ */ - public void join() throws InterruptedException + public void join() throws InterruptedException { getThreadPool().join(); } @@ -489,8 +489,8 @@ public class Server extends HandlerWrapper implements Attributes /** * Add an associated bean. * The bean will be added to the servers {@link Container} - * and if it is a {@link LifeCycle} instance, it will be - * started/stopped along with the Server. Any beans that are also + * and if it is a {@link LifeCycle} instance, it will be + * started/stopped along with the Server. Any beans that are also * {@link Destroyable}, will be destroyed with the server. * @param o the bean object to add */ @@ -506,7 +506,7 @@ public class Server extends HandlerWrapper implements Attributes } /** - * Remove a LifeCycle object to be started/stopped + * Remove a LifeCycle object to be started/stopped * along with the Server * @deprecated Use {@link #removeBean(Object)} */ @@ -515,7 +515,7 @@ public class Server extends HandlerWrapper implements Attributes { removeBean(c); } - + /* ------------------------------------------------------------ */ /** * Remove an associated bean. @@ -532,7 +532,7 @@ public class Server extends HandlerWrapper implements Attributes } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.util.AttributesMap#clearAttributes() */ public void clearAttributes() @@ -541,7 +541,7 @@ public class Server extends HandlerWrapper implements Attributes } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String) */ public Object getAttribute(String name) @@ -550,7 +550,7 @@ public class Server extends HandlerWrapper implements Attributes } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.util.AttributesMap#getAttributeNames() */ public Enumeration getAttributeNames() @@ -559,7 +559,7 @@ public class Server extends HandlerWrapper implements Attributes } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String) */ public void removeAttribute(String name) @@ -568,7 +568,7 @@ public class Server extends HandlerWrapper implements Attributes } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object) */ public void setAttribute(String name, Object attribute) @@ -587,13 +587,13 @@ public class Server extends HandlerWrapper implements Attributes /* ------------------------------------------------------------ */ /** - * Set graceful shutdown timeout. If set, the internal doStop() method will not immediately stop the + * Set graceful shutdown timeout. If set, the internal doStop() method will not immediately stop the * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests - * will be accepted, but existing requests can complete. The server will then wait the configured timeout + * will be accepted, but existing requests can complete. The server will then wait the configured timeout * before stopping. * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server. - * + * */ public void setGracefulShutdown(int timeoutMS) { @@ -612,9 +612,23 @@ public class Server extends HandlerWrapper implements Attributes public void dump(Appendable out,String indent) throws IOException { dumpThis(out); - dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),TypeUtil.asList(_connectors)); + dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),TypeUtil.asList(_connectors)); } - + + + /* ------------------------------------------------------------ */ + public boolean isUncheckedPrintWriter() + { + return _uncheckedPrintWriter; + } + + /* ------------------------------------------------------------ */ + public void setUncheckedPrintWriter(boolean unchecked) + { + _uncheckedPrintWriter=unchecked; + } + + /* ------------------------------------------------------------ */ /* A handler that can be gracefully shutdown. * Called by doStop if a {@link #setGracefulShutdown} period is set. diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java index b3b5eec837a..28efa1bef72 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java @@ -17,8 +17,8 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketException; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; import org.eclipse.jetty.http.HttpException; @@ -29,9 +29,10 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.bio.SocketEndPoint; import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.BlockingHttpConnection; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -81,7 +82,7 @@ public class SocketConnector extends AbstractConnector _localPort=_serverSocket.getLocalPort(); if (_localPort<=0) throw new IllegalStateException("port not allocated for "+this); - + } /* ------------------------------------------------------------ */ @@ -155,27 +156,35 @@ public class SocketConnector extends AbstractConnector protected void doStop() throws Exception { super.doStop(); - Set set=null; - + Set set = new HashSet(); synchronized(_connections) { - set= new HashSet(_connections); + set.addAll(_connections); } - - Iterator iter=set.iterator(); - while(iter.hasNext()) + for (EndPoint endPoint : set) { - ConnectorEndPoint connection = (ConnectorEndPoint)iter.next(); + ConnectorEndPoint connection = (ConnectorEndPoint)endPoint; connection.close(); } } + @Override + public void dump(Appendable out, String indent) throws IOException + { + super.dump(out, indent); + Set connections = new HashSet(); + synchronized (_connections) + { + connections.addAll(_connections); + } + AggregateLifeCycle.dump(out, indent, connections); + } + /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ protected class ConnectorEndPoint extends SocketEndPoint implements Runnable, ConnectedEndPoint { - boolean _dispatched=false; volatile Connection _connection; protected final Socket _socket; @@ -193,7 +202,7 @@ public class SocketConnector extends AbstractConnector public void setConnection(Connection connection) { - if (_connection!=connection) + if (_connection!=connection && _connection!=null) connectionUpgraded(_connection,connection); _connection=connection; } @@ -219,8 +228,8 @@ public class SocketConnector extends AbstractConnector @Override public void close() throws IOException { - if (_connection instanceof HttpConnection) - ((HttpConnection)_connection).getRequest().getAsyncContinuation().cancel(); + if (_connection instanceof AbstractHttpConnection) + ((AbstractHttpConnection)_connection).getRequest().getAsyncContinuation().cancel(); super.close(); } @@ -251,6 +260,12 @@ public class SocketConnector extends AbstractConnector try{close();} catch(IOException e2){LOG.ignore(e2);} } + catch (SocketException e) + { + LOG.debug("EOF", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } catch (HttpException e) { LOG.debug("BAD", e); @@ -277,7 +292,7 @@ public class SocketConnector extends AbstractConnector if (!_socket.isClosed()) { long timestamp=System.currentTimeMillis(); - int max_idle=getMaxIdleTime(); + int max_idle=getMaxIdleTime(); _socket.setSoTimeout(getMaxIdleTime()); int c=0; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java index 47c086b0170..9ddd15bb252 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java @@ -10,21 +10,24 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; import org.eclipse.jetty.io.nio.IndirectNIOBuffer; import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.HostMap; @@ -32,7 +35,6 @@ import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.ThreadPool; /** @@ -231,7 +233,7 @@ public class ConnectHandler extends HandlerWrapper // 1. when this unread data is written and the server replies before the clientToProxy // connection is installed (it is only installed after returning from this method) // 2. when the client sends data before this unread data has been written. - HttpConnection httpConnection = HttpConnection.getCurrentConnection(); + AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection(); Buffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer(); Buffer bodyBuffer = ((HttpParser)httpConnection.getParser()).getBodyBuffer(); int length = headerBuffer == null ? 0 : headerBuffer.length(); @@ -271,7 +273,7 @@ public class ConnectHandler extends HandlerWrapper private ClientToProxyConnection prepareConnections(ConcurrentMap context, SocketChannel channel, Buffer buffer) { - HttpConnection httpConnection = HttpConnection.getCurrentConnection(); + AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection(); ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer); ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp()); clientToProxy.setConnection(proxyToServer); @@ -421,17 +423,18 @@ public class ConnectHandler extends HandlerWrapper private class Manager extends SelectorManager { @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey selectionKey) throws IOException + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException { - SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey); + SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, key, channel.socket().getSoTimeout()); + endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); endp.setMaxIdleTime(_writeTimeout); return endp; } @Override - protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) { - ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment(); + ProxyToServerConnection proxyToServer = (ProxyToServerConnection)attachment; proxyToServer.setTimeStamp(System.currentTimeMillis()); proxyToServer.setEndPoint(endpoint); return proxyToServer; @@ -461,7 +464,9 @@ public class ConnectHandler extends HandlerWrapper } } - public class ProxyToServerConnection implements Connection + + + public class ProxyToServerConnection implements AsyncConnection { private final CountDownLatch _ready = new CountDownLatch(1); private final Buffer _buffer = new IndirectNIOBuffer(1024); @@ -469,7 +474,7 @@ public class ConnectHandler extends HandlerWrapper private volatile Buffer _data; private volatile ClientToProxyConnection _toClient; private volatile long _timestamp; - private volatile SelectChannelEndPoint _endPoint; + private volatile AsyncEndPoint _endPoint; public ProxyToServerConnection(ConcurrentMap context, Buffer data) { @@ -541,6 +546,11 @@ public class ConnectHandler extends HandlerWrapper } } + public void onInputShutdown() throws IOException + { + // TODO + } + private void writeData() throws IOException { // This method is called from handle() and closeServer() @@ -581,7 +591,7 @@ public class ConnectHandler extends HandlerWrapper _timestamp = timestamp; } - public void setEndPoint(SelectChannelEndPoint endpoint) + public void setEndPoint(AsyncEndPoint endpoint) { _endPoint = endpoint; } @@ -596,7 +606,7 @@ public class ConnectHandler extends HandlerWrapper return false; } - public void closed() + public void onClose() { } @@ -657,7 +667,7 @@ public class ConnectHandler extends HandlerWrapper _endPoint.shutdownOutput(); } - public void idleExpired() + public void onIdleExpired(long idleForMs) { try { @@ -671,7 +681,7 @@ public class ConnectHandler extends HandlerWrapper } } - public class ClientToProxyConnection implements Connection + public class ClientToProxyConnection implements AsyncConnection { private final Buffer _buffer = new IndirectNIOBuffer(1024); private final ConcurrentMap _context; @@ -758,6 +768,11 @@ public class ConnectHandler extends HandlerWrapper _logger.debug("{}: end reading from client", this); } } + + public void onInputShutdown() throws IOException + { + // TODO + } public long getTimeStamp() { @@ -774,7 +789,7 @@ public class ConnectHandler extends HandlerWrapper return false; } - public void closed() + public void onClose() { } @@ -819,7 +834,7 @@ public class ConnectHandler extends HandlerWrapper _endPoint.shutdownOutput(); } - public void idleExpired() + public void onIdleExpired(long idleForMs) { try { 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 42feb615a0c..3538d75f96d 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 @@ -30,7 +30,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; @@ -48,11 +47,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.DispatcherType; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HandlerContainer; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Attributes; @@ -65,19 +64,18 @@ import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; /* ------------------------------------------------------------ */ /** * ContextHandler. - * + * * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader. - * + * *

    * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX. - * + * * @org.apache.xbean.XBean description="Creates a basic HTTP context" */ public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful @@ -96,7 +94,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Get the current ServletContext implementation. - * + * * @return ServletContext implementation */ public static Context getCurrentContext() @@ -122,6 +120,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. private EventListener[] _eventListeners; private Logger _logger; private boolean _allowNullPathInfo; + private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue(); private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue(); private boolean _compactPath = false; private boolean _aliases = false; @@ -245,7 +244,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a * matching virtual host name. - * + * * @param vhosts * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. @@ -264,12 +263,91 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } } + /* ------------------------------------------------------------ */ + /** Either set virtual hosts or add to an existing set of virtual hosts. + * + * @param virtualHosts + * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be + * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public void addVirtualHosts(String[] virtualHosts) + { + if (virtualHosts == null) // since this is add, we don't null the old ones + { + return; + } + else + { + List currentVirtualHosts = null; + if (_vhosts != null) + { + currentVirtualHosts = new ArrayList(Arrays.asList(_vhosts)); + } + else + { + currentVirtualHosts = new ArrayList(); + } + + for (int i = 0; i < virtualHosts.length; i++) + { + String normVhost = normalizeHostname(virtualHosts[i]); + if (!currentVirtualHosts.contains(normVhost)) + { + currentVirtualHosts.add(normVhost); + } + } + _vhosts = currentVirtualHosts.toArray(new String[0]); + } + } + + /* ------------------------------------------------------------ */ + /** + * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null + * + * @param virtualHosts + * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be + * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public void removeVirtualHosts(String[] virtualHosts) + { + if (virtualHosts == null) + { + return; // do nothing + } + else if ( _vhosts == null || _vhosts.length == 0) + { + return; // do nothing + } + else + { + List existingVirtualHosts = new ArrayList(Arrays.asList(_vhosts)); + + for (int i = 0; i < virtualHosts.length; i++) + { + String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]); + if (existingVirtualHosts.contains(toRemoveVirtualHost)) + { + existingVirtualHosts.remove(toRemoveVirtualHost); + } + } + + if (existingVirtualHosts.isEmpty()) + { + _vhosts = null; // if we ended up removing them all, just null out _vhosts + } + else + { + _vhosts = existingVirtualHosts.toArray(new String[0]); + } + } + } + /* ------------------------------------------------------------ */ /** * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a * matching virtual host name. - * + * * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String * representation of IP addresses. Host names may start with '*.' to wildcard one level of names. */ @@ -293,9 +371,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Set the names of accepted connectors. - * + * * Names are either "host:port" or a specific configured name for a connector. - * + * * @param connectors * If non null, an array of connector names that this context will accept a request from. */ @@ -347,7 +425,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Make best effort to extract a file classpath from the context classloader - * + * * @return Returns the classLoader. */ public String getClassPath() @@ -443,7 +521,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Set the context event listeners. - * + * * @param eventListeners * the event listeners * @see ServletContextListener @@ -481,7 +559,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Add a context event listeners. - * + * * @see ServletContextListener * @see ServletContextAttributeListener * @see ServletRequestListener @@ -498,14 +576,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. */ public boolean isShutdown() { - return !_shutdown; + synchronized (this) + { + return !_shutdown; + } } /* ------------------------------------------------------------ */ /** * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing * requests can complete, but no new requests are accepted. - * + * * @param shutdown * true if this context is (not?) accepting new requests */ @@ -524,7 +605,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. */ public boolean isAvailable() { - return _available; + synchronized (this) + { + return _available; + } } /* ------------------------------------------------------------ */ @@ -588,7 +672,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. // defers the calling of super.doStart() startContext(); - _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE; + synchronized(this) + { + _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE; + } } finally { @@ -607,7 +694,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /** * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers. - * + * * @see org.eclipse.jetty.server.handler.ContextHandler.Context */ protected void startContext() throws Exception @@ -755,7 +842,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. // Check the connector if (_connectors != null && _connectors.size() > 0) { - String connector = HttpConnection.getCurrentConnection().getConnector().getName(); + String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName(); if (connector == null || !_connectors.contains(connector)) return false; } @@ -793,7 +880,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. @Override public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - LOG.debug("scope {} @ {}",baseRequest.getContextPath() + "|" + baseRequest.getServletPath() + "|" + baseRequest.getPathInfo(),this); + if (LOG.isDebugEnabled()) + LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this); Context old_context = null; String old_context_path = null; @@ -801,7 +889,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. String old_path_info = null; ClassLoader old_classloader = null; Thread current_thread = null; - String pathInfo = null; + String pathInfo = target; DispatcherType dispatch = baseRequest.getDispatcherType(); @@ -865,7 +953,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } if (LOG.isDebugEnabled()) - LOG.debug("context={} @ {}",baseRequest.getContextPath() + "|" + baseRequest.getServletPath() + "|" + baseRequest.getPathInfo(),this); + LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this); // start manual inline of nextScope(target,baseRequest,request,response); if (never()) @@ -1028,7 +1116,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of * a context. No attribute listener events are triggered by this API. - * + * * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) */ public void setAttribute(String name, Object value) @@ -1261,11 +1349,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ + /** + * Set the maximum size of a form post, to protect against DOS attacks from large forms. + * @param maxSize + */ public void setMaxFormContentSize(int maxSize) { _maxFormContentSize = maxSize; } + /* ------------------------------------------------------------ */ + public int getMaxFormKeys() + { + return _maxFormKeys; + } + + /* ------------------------------------------------------------ */ + /** + * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys. + * @param max + */ + public void setMaxFormKeys(int max) + { + _maxFormKeys = max; + } + /* ------------------------------------------------------------ */ /** * @return True if URLs are compacted to replace multiple '/'s with a single '/' @@ -1293,14 +1401,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. StringBuilder b = new StringBuilder(); - String p = getClass().getPackage().getName(); - if (p != null && p.length() > 0) + Package pkg = getClass().getPackage(); + if (pkg != null) { - String[] ss = p.split("\\."); - for (String s : ss) - b.append(s.charAt(0)).append('.'); + String p = pkg.getName(); + if (p != null && p.length() > 0) + { + String[] ss = p.split("\\."); + for (String s : ss) + b.append(s.charAt(0)).append('.'); + } } - b.append(getClass().getSimpleName()); b.append('{').append(getContextPath()).append(',').append(getBaseResource()); @@ -1343,7 +1454,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /** * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale * language is looked up. - * + * * @param locale * a Locale value * @return a String representing the character encoding for the locale or null if none found. @@ -1407,7 +1518,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}. - * + * * @param urlOrPath * The URL or path to convert * @return The Resource for the URL/path @@ -1469,8 +1580,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. *

    * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}. *

    - * - * + * + * */ public class Context implements ServletContext { @@ -1699,7 +1810,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. URL url = getResource(path); if (url == null) return null; - return url.openStream(); + Resource r = Resource.newResource(url); + return r.getInputStream(); } catch (Exception e) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java index 5f424466636..6ac3dcc2ff2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -29,9 +29,9 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.ByteArrayISO8859Writer; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; /* ------------------------------------------------------------ */ @@ -61,7 +61,10 @@ public class DefaultHandler extends AbstractHandler { URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico"); if (fav!=null) - _favicon=IO.readBytes(fav.openStream()); + { + Resource r = Resource.newResource(fav); + _favicon=IO.readBytes(r.getInputStream()); + } } catch(Exception e) { @@ -110,10 +113,6 @@ public class DefaultHandler extends AbstractHandler response.setContentType(MimeTypes.TEXT_HTML); ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500); - - String uri=request.getRequestURI(); - uri=StringUtil.replace(uri,"<","<"); - uri=StringUtil.replace(uri,">",">"); writer.write("\n\nError 404 - Not Found"); writer.write("\n\n

    Error 404 - Not Found.

    \n"); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java index 6a422e9be84..b43e34b9cef 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -23,7 +23,7 @@ import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.ByteArrayISO8859Writer; @@ -45,7 +45,7 @@ public class ErrorHandler extends AbstractHandler */ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - HttpConnection connection = HttpConnection.getCurrentConnection(); + AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); connection.getRequest().setHandled(true); String method = request.getMethod(); if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD)) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java index 7aead3c0c3a..81198f5299d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java @@ -159,7 +159,7 @@ public class GzipHandler extends HandlerWrapper * * @return the buffer size */ - public int setBufferSize() + public int getBufferSize() { return _bufferSize; } @@ -281,9 +281,9 @@ public class GzipHandler extends HandlerWrapper return new GzipResponseWrapper(request, response) { { - setMimeTypes(GzipHandler.this._mimeTypes); - setBufferSize(GzipHandler.this._bufferSize); - setMinGzipSize(GzipHandler.this._minGzipSize); + super.setMimeTypes(GzipHandler.this._mimeTypes); + super.setBufferSize(GzipHandler.this._bufferSize); + super.setMinGzipSize(GzipHandler.this._minGzipSize); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java index 2d597ed5c30..de8c0e4a2e0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java index 060e3288323..4e6e50b52c6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java @@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.IPAddressMap; import org.eclipse.jetty.util.log.Log; @@ -179,7 +179,7 @@ public class IPAccessHandler extends HandlerWrapper public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Get the real remote IP (not the one set by the forwarded headers (which may be forged)) - HttpConnection connection = baseRequest.getConnection(); + AbstractHttpConnection connection = baseRequest.getConnection(); if (connection!=null) { EndPoint endp=connection.getEndPoint(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java index 8650b80c9a1..6ff990c9c04 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java @@ -100,12 +100,6 @@ public class MovedContextHandler extends ContextHandler { if (_newContextURL==null) return; - - String url = _newContextURL; - if (!_discardPathInfo && request.getPathInfo()!=null) - url=URIUtil.addPaths(url, request.getPathInfo()); - if (!_discardQuery && request.getQueryString()!=null) - url+="?"+request.getQueryString(); String path=_newContextURL; if (!_discardPathInfo && request.getPathInfo()!=null) @@ -117,7 +111,9 @@ public class MovedContextHandler extends ContextHandler if (!_discardQuery && request.getQueryString()!=null) { location.append('?'); - location.append(request.getQueryString()); + String q=request.getQueryString(); + q=q.replaceAll("\r\n?&=","!"); + location.append(q); } response.setHeader(HttpHeaders.LOCATION,location.toString()); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 87be3d7b3fd..f4978c67494 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -29,7 +29,8 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.WriterOutputStream; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.handler.ContextHandler.Context; @@ -45,12 +46,12 @@ import org.eclipse.jetty.util.resource.Resource; * * This handle will serve static content and handle If-Modified-Since headers. * No caching is done. - * Requests that cannot be handled are let pass (Eg no 404's) + * Requests for resources that do not exist are let pass (Eg no 404's). * * * @org.apache.xbean.XBean */ -public class ResourceHandler extends AbstractHandler +public class ResourceHandler extends HandlerWrapper { private static final Logger LOG = Log.getLogger(ResourceHandler.class); @@ -205,7 +206,7 @@ public class ResourceHandler extends AbstractHandler { try { - _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-default.css")); + _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css")); } catch(IOException e) { @@ -292,10 +293,28 @@ public class ResourceHandler extends AbstractHandler /* ------------------------------------------------------------ */ protected Resource getResource(HttpServletRequest request) throws MalformedURLException { - String path_info=request.getPathInfo(); - if (path_info==null) - return null; - return getResource(path_info); + String servletPath; + String pathInfo; + Boolean included = request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI) != null; + if (included != null && included.booleanValue()) + { + servletPath = (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH); + pathInfo = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO); + + if (servletPath == null && pathInfo == null) + { + servletPath = request.getServletPath(); + pathInfo = request.getPathInfo(); + } + } + else + { + servletPath = request.getServletPath(); + pathInfo = request.getPathInfo(); + } + + String pathInContext=URIUtil.addPaths(servletPath,pathInfo); + return getResource(pathInContext); } @@ -326,7 +345,7 @@ public class ResourceHandler extends AbstractHandler /* ------------------------------------------------------------ */ /* - * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) */ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { @@ -334,23 +353,35 @@ public class ResourceHandler extends AbstractHandler return; boolean skipContentBody = false; + if(!HttpMethods.GET.equals(request.getMethod())) { if(!HttpMethods.HEAD.equals(request.getMethod())) + { + //try another handler + super.handle(target, baseRequest, request, response); return; + } skipContentBody = true; } Resource resource = getResource(request); + if (resource==null || !resource.exists()) { - if (target.endsWith("/jetty-stylesheet.css")) - { - response.setContentType("text/css"); + if (target.endsWith("/jetty-dir.css")) + { resource = getStylesheet(); + if (resource==null) + return; + response.setContentType("text/css"); } else + { + //no resource - try other handlers + super.handle(target, baseRequest, request, response); return; + } } if (!_aliases && resource.getAlias()!=null) @@ -359,7 +390,7 @@ public class ResourceHandler extends AbstractHandler return; } - // We are going to server something + // We are going to serve something baseRequest.setHandled(true); if (resource.isDirectory()) @@ -408,10 +439,10 @@ public class ResourceHandler extends AbstractHandler catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());} // See if a short direct method can be used? - if (out instanceof HttpConnection.Output) + if (out instanceof AbstractHttpConnection.Output) { // TODO file mapped buffers - ((HttpConnection.Output)out).sendContent(resource.getInputStream()); + ((AbstractHttpConnection.Output)out).sendContent(resource.getInputStream()); } else { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java new file mode 100644 index 00000000000..aa4edeb7f61 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java @@ -0,0 +1,165 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.handler; + +import java.io.IOException; + +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.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being + * made. + * + * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687 + * + * Usage: + * + *
    +    Server server = new Server(8080);
    +    HandlerList handlers = new HandlerList();
    +    handlers.setHandlers(new Handler[]
    +    { someOtherHandler, new ShutdownHandler(server,"secret password") });
    +    server.setHandler(handlers);
    +    server.start();
    +   
    + * +
    +   public static void attemptShutdown(int port, String shutdownCookie) {
    +        try {
    +            URL url = new URL("http://localhost:" + port + "/shutdown?cookie=" + shutdownCookie);
    +            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
    +            connection.setRequestMethod("POST");
    +            connection.getResponseCode();
    +            logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
    +        } catch (SocketException e) {
    +            logger.debug("Not running");
    +            // Okay - the server is not running
    +        } catch (IOException e) {
    +            throw new RuntimeException(e);
    +        }
    +    }
    +  
    + */ +public class ShutdownHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(ShutdownHandler.class); + + private final String _shutdownToken; + + private final Server _server; + + private boolean _exitJvm = false; + + + + /** + * Creates a listener that lets the server be shut down remotely (but only from localhost). + * + * @param server + * the Jetty instance that should be shut down + * @param shutdownToken + * a secret password to avoid unauthorized shutdown attempts + */ + public ShutdownHandler(Server server, String shutdownToken) + { + this._server = server; + this._shutdownToken = shutdownToken; + } + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!target.equals("/shutdown")) + { + return; + } + + if (!request.getMethod().equals("POST")) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + if (!hasCorrectSecurityToken(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + if (!requestFromLocalhost(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + LOG.info("Shutting down by request from " + getRemoteAddr(request)); + + new Thread() + { + public void run () + { + try + { + shutdownServer(); + } + catch (InterruptedException e) + { + LOG.ignore(e); + } + catch (Exception e) + { + throw new RuntimeException("Shutting down server",e); + } + } + }.start(); + } + + private boolean requestFromLocalhost(HttpServletRequest request) + { + return "127.0.0.1".equals(getRemoteAddr(request)); + } + + protected String getRemoteAddr(HttpServletRequest request) + { + return request.getRemoteAddr(); + } + + private boolean hasCorrectSecurityToken(HttpServletRequest request) + { + return _shutdownToken.equals(request.getParameter("token")); + } + + private void shutdownServer() throws Exception + { + _server.stop(); + + if (_exitJvm) + { + System.exit(0); + } + } + + public void setExitJvm(boolean exitJvm) + { + this._exitJvm = exitJvm; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java index 69639a9d631..8e331142e6a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java @@ -4,19 +4,20 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== - + package org.eclipse.jetty.server.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Set; @@ -29,7 +30,6 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.nio.ChannelEndPoint; import org.eclipse.jetty.server.BlockingHttpConnection; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.log.Log; @@ -40,25 +40,25 @@ import org.eclipse.jetty.util.log.Logger; /** Blocking NIO connector. * This connector uses efficient NIO buffers with a traditional blocking thread model. * Direct NIO buffers are used and a thread is allocated per connections. - * + * * This connector is best used when there are a few very active connections. - * + * * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector" - * - * + * + * * */ -public class BlockingChannelConnector extends AbstractNIOConnector +public class BlockingChannelConnector extends AbstractNIOConnector { private static final Logger LOG = Log.getLogger(BlockingChannelConnector.class); private transient ServerSocketChannel _acceptChannel; private final Set _endpoints = new ConcurrentHashSet(); - - + + /* ------------------------------------------------------------ */ /** Constructor. - * + * */ public BlockingChannelConnector() { @@ -104,12 +104,12 @@ public class BlockingChannelConnector extends AbstractNIOConnector } } } - + }); - + } - + /* ------------------------------------------------------------ */ public void open() throws IOException { @@ -129,12 +129,12 @@ public class BlockingChannelConnector extends AbstractNIOConnector _acceptChannel.close(); _acceptChannel=null; } - + /* ------------------------------------------------------------ */ @Override public void accept(int acceptorID) throws IOException, InterruptedException - { + { SocketChannel channel = _acceptChannel.accept(); channel.configureBlocking(true); Socket socket=channel.socket(); @@ -143,7 +143,7 @@ public class BlockingChannelConnector extends AbstractNIOConnector BlockingChannelEndPoint connection=new BlockingChannelEndPoint(channel); connection.dispatch(); } - + /* ------------------------------------------------------------------------------- */ @Override public void customize(EndPoint endpoint, Request request) @@ -162,7 +162,7 @@ public class BlockingChannelConnector extends AbstractNIOConnector return -1; return _acceptChannel.socket().getLocalPort(); } - + /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ @@ -171,14 +171,14 @@ public class BlockingChannelConnector extends AbstractNIOConnector private Connection _connection; private int _timeout; private volatile long _idleTimestamp; - - BlockingChannelEndPoint(ByteChannel channel) + + BlockingChannelEndPoint(ByteChannel channel) throws IOException { super(channel,BlockingChannelConnector.this._maxIdleTime); _connection = new BlockingHttpConnection(BlockingChannelConnector.this,this,getServer()); } - + /* ------------------------------------------------------------ */ /** Get the connection. * @return the connection @@ -187,7 +187,7 @@ public class BlockingChannelConnector extends AbstractNIOConnector { return _connection; } - + /* ------------------------------------------------------------ */ public void setConnection(Connection connection) { @@ -208,24 +208,24 @@ public class BlockingChannelConnector extends AbstractNIOConnector { try { - close(); + super.close(); } catch (IOException e) { LOG.ignore(e); } } - + /* ------------------------------------------------------------ */ void dispatch() throws IOException { if (!getThreadPool().dispatch(this)) { LOG.warn("dispatch failed for {}",_connection); - BlockingChannelEndPoint.this.close(); + super.close(); } } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.nio.ChannelEndPoint#fill(org.eclipse.jetty.io.Buffer) @@ -289,9 +289,9 @@ public class BlockingChannelConnector extends AbstractNIOConnector _timeout=getMaxIdleTime(); } } - + _connection = _connection.handle(); - + } } catch (EofException e) @@ -303,27 +303,27 @@ public class BlockingChannelConnector extends AbstractNIOConnector catch (HttpException e) { LOG.debug("BAD", e); - try{BlockingChannelEndPoint.this.close();} + try{super.close();} catch(IOException e2){LOG.ignore(e2);} } catch(Throwable e) { LOG.warn("handle failed",e); - try{BlockingChannelEndPoint.this.close();} + try{super.close();} catch(IOException e2){LOG.ignore(e2);} } finally { connectionClosed(_connection); _endpoints.remove(this); - + // wait for client to close, but if not, close ourselves. try { if (!_socket.isClosed()) { long timestamp=System.currentTimeMillis(); - int max_idle=getMaxIdleTime(); + int max_idle=getMaxIdleTime(); _socket.setSoTimeout(getMaxIdleTime()); int c=0; @@ -342,5 +342,20 @@ public class BlockingChannelConnector extends AbstractNIOConnector } } } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}", + hashCode(), + _socket.getRemoteSocketAddress(), + _socket.getLocalSocketAddress(), + isOpen(), + isInputShutdown(), + isOutputShutdown(), + _connection); + } + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java index a5f9741afd5..7bba75c1935 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java @@ -55,7 +55,7 @@ public class InheritedChannelConnector extends SelectChannelConnector LOG.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort()); if ( _acceptChannel != null ) - _acceptChannel.configureBlocking(false); + _acceptChannel.configureBlocking(true); } catch(NoSuchMethodError e) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java index efea044745b..072de4cf778 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java @@ -54,6 +54,7 @@ public class NetworkTrafficSelectChannelConnector extends SelectChannelConnector protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key) throws IOException { NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, _maxIdleTime, listeners); + endPoint.setConnection(selectSet.getManager().newConnection(channel,endPoint, key.attachment())); endPoint.notifyOpened(); return endPoint; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java index c8cff99341c..b6165d45fdf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java @@ -22,22 +22,20 @@ import java.nio.channels.SocketChannel; import java.util.Arrays; import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.io.nio.SelectorManager; import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; import org.eclipse.jetty.server.AsyncHttpConnection; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.ThreadPool; -import org.eclipse.jetty.util.thread.Timeout.Task; /* ------------------------------------------------------------------------------- */ /** @@ -91,10 +89,15 @@ public class SelectChannelConnector extends AbstractNIOConnector @Override public void accept(int acceptorID) throws IOException { - ServerSocketChannel server = _acceptChannel; - if (server!=null && server.isOpen()) + ServerSocketChannel server; + synchronized(this) { - SocketChannel channel = _acceptChannel.accept(); + server = _acceptChannel; + } + + if (server!=null && server.isOpen() && _manager.isStarted()) + { + SocketChannel channel = server.accept(); channel.configureBlocking(false); Socket socket = channel.socket(); configure(socket); @@ -118,9 +121,8 @@ public class SelectChannelConnector extends AbstractNIOConnector @Override public void customize(EndPoint endpoint, Request request) throws IOException { - SelectChannelEndPoint cep = ((SelectChannelEndPoint)endpoint); - cep.cancelIdle(); - request.setTimeStamp(cep.getSelectSet().getNow()); + AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint); + request.setTimeStamp(System.currentTimeMillis()); endpoint.setMaxIdleTime(_maxIdleTime); super.customize(endpoint, request); } @@ -129,12 +131,19 @@ public class SelectChannelConnector extends AbstractNIOConnector @Override public void persist(EndPoint endpoint) throws IOException { - ((SelectChannelEndPoint)endpoint).scheduleIdle(); + AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint); + aEndp.setCheckForIdle(true); super.persist(endpoint); } /* ------------------------------------------------------------ */ - public Object getConnection() + public SelectorManager getSelectorManager() + { + return _manager; + } + + /* ------------------------------------------------------------ */ + public synchronized Object getConnection() { return _acceptChannel; } @@ -239,9 +248,9 @@ public class SelectChannelConnector extends AbstractNIOConnector _manager.setMaxIdleTime(getMaxIdleTime()); _manager.setLowResourcesConnections(getLowResourcesConnections()); _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime()); - _manager.start(); super.doStart(); + _manager.start(); } /* ------------------------------------------------------------ */ @@ -271,7 +280,9 @@ public class SelectChannelConnector extends AbstractNIOConnector /* ------------------------------------------------------------ */ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException { - return new SelectChannelEndPoint(channel,selectSet,key, SelectChannelConnector.this._maxIdleTime); + SelectChannelEndPoint endp= new SelectChannelEndPoint(channel,selectSet,key, SelectChannelConnector.this._maxIdleTime); + endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); + return endp; } /* ------------------------------------------------------------------------------- */ @@ -281,48 +292,24 @@ public class SelectChannelConnector extends AbstractNIOConnector } /* ------------------------------------------------------------------------------- */ - protected Connection newConnection(SocketChannel channel,final SelectChannelEndPoint endpoint) + protected AsyncConnection newConnection(SocketChannel channel,final AsyncEndPoint endpoint) { - return new SelectChannelHttpConnection(SelectChannelConnector.this,endpoint,getServer(),endpoint); + return new AsyncHttpConnection(SelectChannelConnector.this,endpoint,getServer()); } /* ------------------------------------------------------------ */ public void dump(Appendable out, String indent) throws IOException { - out.append(String.valueOf(this)).append("\n"); - ServerSocketChannel channel=_acceptChannel; + super.dump(out, indent); + ServerSocketChannel channel; + synchronized (this) + { + channel=_acceptChannel; + } if (channel==null) - AggregateLifeCycle.dump(out,indent,Arrays.asList(new Object[]{null,"CLOSED",_manager})); + AggregateLifeCycle.dump(out,indent,Arrays.asList(null,"CLOSED",_manager)); else - AggregateLifeCycle.dump(out,indent,Arrays.asList(new Object[]{_acceptChannel,_acceptChannel.isOpen()?"OPEN":"CLOSED",_manager})); - } - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - private class SelectChannelHttpConnection extends AsyncHttpConnection - { - private final SelectChannelEndPoint _endpoint; - - private SelectChannelHttpConnection(Connector connector, EndPoint endpoint, Server server, SelectChannelEndPoint endpoint2) - { - super(connector,endpoint,server); - _endpoint = endpoint2; - } - - /* ------------------------------------------------------------ */ - @Override - public void cancelTimeout(Task task) - { - _endpoint.getSelectSet().cancelTimeout(task); - } - - /* ------------------------------------------------------------ */ - @Override - public void scheduleTimeout(Task task, long timeoutMs) - { - _endpoint.getSelectSet().scheduleTimeout(task,timeoutMs); - } + AggregateLifeCycle.dump(out,indent,Arrays.asList(channel,channel.isOpen()?"OPEN":"CLOSED",_manager)); } /* ------------------------------------------------------------ */ @@ -359,7 +346,7 @@ public class SelectChannelConnector extends AbstractNIOConnector } @Override - protected Connection newConnection(SocketChannel channel,SelectChannelEndPoint endpoint) + public AsyncConnection newConnection(SocketChannel channel,AsyncEndPoint endpoint, Object attachment) { return SelectChannelConnector.this.newConnection(channel,endpoint); } 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 8712f766a59..73a4b6f81b8 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 @@ -30,7 +30,7 @@ import org.eclipse.jetty.util.log.Logger; @SuppressWarnings("deprecation") public abstract class AbstractSession implements AbstractSessionManager.SessionIf { - final static Logger __log = SessionHandler.__log; + final static Logger LOG = SessionHandler.LOG; private final AbstractSessionManager _manager; private final String _clusterId; // ID unique within cluster @@ -63,7 +63,8 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI _lastAccessed=_created; _requests=1; _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000:-1; - __log.debug("new session & id "+_nodeId+" "+_clusterId); + if (LOG.isDebugEnabled()) + LOG.debug("new session & id "+_nodeId+" "+_clusterId); } /* ------------------------------------------------------------- */ @@ -76,7 +77,8 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI _accessed=accessed; _lastAccessed=accessed; _requests=1; - __log.debug("new session "+_nodeId+" "+_clusterId); + if (LOG.isDebugEnabled()) + LOG.debug("new session "+_nodeId+" "+_clusterId); } /* ------------------------------------------------------------- */ @@ -300,7 +302,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI { try { - __log.debug("invalidate ",_clusterId); + LOG.debug("invalidate {}",_clusterId); if (isValid()) clearAttributes(); } @@ -455,7 +457,10 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI /* ------------------------------------------------------------- */ protected void cookieSet() { - _cookieSet=_accessed; + synchronized (this) + { + _cookieSet=_accessed; + } } /* ------------------------------------------------------------ */ 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 d0c53610c5b..d93fc79c694 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 @@ -78,7 +78,7 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme } /* ------------------------------------------------------------ */ - public void setRandom(Random random) + public synchronized void setRandom(Random random) { _random=random; _weakRandom=false; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java index 8f433155602..b6c1a381668 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java @@ -56,7 +56,7 @@ import org.eclipse.jetty.util.statistic.SampleStatistic; @SuppressWarnings("deprecation") public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager { - final static Logger __log = SessionHandler.__log; + final static Logger __log = SessionHandler.LOG; /* ------------------------------------------------------------ */ public final static int __distantFuture=60*60*24*7*52*20; @@ -734,7 +734,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement { HttpSessionEvent event=new HttpSessionEvent(session); for (HttpSessionListener listener : _sessionListeners) - listener.sessionCreated(event); + listener.sessionDestroyed(event); } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java index 5e0aea6b457..7624afcf87a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java @@ -14,7 +14,9 @@ package org.eclipse.jetty.server.session; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -46,6 +48,34 @@ public class HashSessionIdManager extends AbstractSessionIdManager super(random); } + /* ------------------------------------------------------------ */ + /** + * @return Collection of String session IDs + */ + public Collection getSessions() + { + return Collections.unmodifiableCollection(_sessions.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of Sessions for the passed session ID + */ + public Collection getSession(String id) + { + ArrayList sessions = new ArrayList(); + Set> refs =_sessions.get(id); + if (refs!=null) + { + for (WeakReference ref: refs) + { + HttpSession session = ref.get(); + if (session!=null) + sessions.add(session); + } + } + return sessions; + } /* ------------------------------------------------------------ */ /** Get the session ID with any worker 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 c015eb283f0..3b59213b516 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 @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server.session; @@ -42,12 +42,12 @@ import org.eclipse.jetty.util.log.Logger; *

    * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler. - * + * */ public class HashSessionManager extends AbstractSessionManager { - final static Logger __log = SessionHandler.__log; - + final static Logger __log = SessionHandler.LOG; + protected final ConcurrentMap _sessions=new ConcurrentHashMap(); private static int __id; private Timer _timer; @@ -60,7 +60,7 @@ public class HashSessionManager extends AbstractSessionManager File _storeDir; private boolean _lazyLoad=false; private volatile boolean _sessionsLoaded=false; - + /* ------------------------------------------------------------ */ public HashSessionManager() { @@ -85,7 +85,7 @@ public class HashSessionManager extends AbstractSessionManager _timerStop=true; _timer=new Timer("HashSessionScavenger-"+__id++, true); } - + setScavengePeriod(getScavengePeriod()); if (_storeDir!=null) @@ -96,7 +96,7 @@ public class HashSessionManager extends AbstractSessionManager if (!_lazyLoad) restoreSessions(); } - + setSavePeriod(getSavePeriod()); } @@ -106,7 +106,7 @@ public class HashSessionManager extends AbstractSessionManager */ @Override public void doStop() throws Exception - { + { // stop the scavengers synchronized(this) { @@ -120,16 +120,16 @@ public class HashSessionManager extends AbstractSessionManager _timer.cancel(); _timer=null; } - - // This will callback invalidate sessions - where we decide if we will save + + // This will callback invalidate sessions - where we decide if we will save super.doStop(); - + _sessions.clear(); } /* ------------------------------------------------------------ */ - /** + /** * @return the period in seconds at which a check is made for sessions to be invalidated. */ public int getScavengePeriod() @@ -153,7 +153,7 @@ public class HashSessionManager extends AbstractSessionManager /* ------------------------------------------------------------ */ /** - * @return seconds Idle period after which a session is saved + * @return seconds Idle period after which a session is saved */ public int getIdleSavePeriod() { @@ -162,15 +162,15 @@ public class HashSessionManager extends AbstractSessionManager return _idleSavePeriodMs / 1000; } - + /* ------------------------------------------------------------ */ /** - * Configures the period in seconds after which a session is deemed idle and saved - * to save on session memory. - * - * The session is persisted, the values attribute map is cleared and the session set to idled. - * - * @param seconds Idle period after which a session is saved + * Configures the period in seconds after which a session is deemed idle and saved + * to save on session memory. + * + * The session is persisted, the values attribute map is cleared and the session set to idled. + * + * @param seconds Idle period after which a session is saved */ public void setIdleSavePeriod(int seconds) { @@ -196,7 +196,7 @@ public class HashSessionManager extends AbstractSessionManager if (period < 0) period=0; _savePeriodMs=period; - + if (_timer!=null) { synchronized (this) @@ -218,7 +218,7 @@ public class HashSessionManager extends AbstractSessionManager { __log.warn(e); } - } + } }; _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs); } @@ -234,10 +234,10 @@ public class HashSessionManager extends AbstractSessionManager { if (_savePeriodMs<=0) return 0; - + return _savePeriodMs/1000; } - + /* ------------------------------------------------------------ */ /** * @param seconds the period in seconds at which a check is made for sessions to be invalidated. @@ -267,13 +267,13 @@ public class HashSessionManager extends AbstractSessionManager public void run() { scavenge(); - } + } }; _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs); } } } - + /* -------------------------------------------------------------- */ /** * Find sessions that have timed out and invalidate them. This runs in the @@ -284,14 +284,14 @@ public class HashSessionManager extends AbstractSessionManager //don't attempt to scavenge if we are shutting down if (isStopping() || isStopped()) return; - + Thread thread=Thread.currentThread(); ClassLoader old_loader=thread.getContextClassLoader(); try { if (_loader!=null) thread.setContextClassLoader(_loader); - + // For each session long now=System.currentTimeMillis(); for (Iterator i=_sessions.values().iterator(); i.hasNext();) @@ -305,23 +305,20 @@ public class HashSessionManager extends AbstractSessionManager } else if (_idleSavePeriodMs>0&&session.getAccessed()+_idleSavePeriodMs sessions=_sessions; if (sessions==null) return null; - + HashedSession session = sessions.get(idInCluster); if (session == null && _lazyLoad) @@ -359,7 +356,7 @@ public class HashSessionManager extends AbstractSessionManager if (_idleSavePeriodMs!=0) session.deIdle(); - + return session; } @@ -387,7 +384,7 @@ public class HashSessionManager extends AbstractSessionManager for (HashedSession session : sessions) session.invalidate(); } - + // check that no new sessions were created while we were iterating sessions=new ArrayList(_sessions.values()); } @@ -399,13 +396,13 @@ public class HashSessionManager extends AbstractSessionManager { return new HashedSession(this, request); } - + /* ------------------------------------------------------------ */ protected AbstractSession newSession(long created, long accessed, String clusterId) { return new HashedSession(this, created,accessed, clusterId); } - + /* ------------------------------------------------------------ */ @Override protected boolean removeSession(String clusterId) @@ -430,7 +427,7 @@ public class HashSessionManager extends AbstractSessionManager { _lazyLoad = lazyLoad; } - + /* ------------------------------------------------------------ */ public boolean isLazyLoad() { @@ -441,7 +438,7 @@ public class HashSessionManager extends AbstractSessionManager public void restoreSessions () throws Exception { _sessionsLoaded = true; - + if (_storeDir==null || !_storeDir.exists()) { return; @@ -468,9 +465,9 @@ public class HashSessionManager extends AbstractSessionManager File file = new File(_storeDir,idInCuster); if (file.exists()) { - FileInputStream in = new FileInputStream(file); + FileInputStream in = new FileInputStream(file); HashedSession session = restoreSession(in, null); - in.close(); + in.close(); addSession(session, false); session.didActivate(); file.delete(); @@ -483,7 +480,7 @@ public class HashSessionManager extends AbstractSessionManager } return null; } - + /* ------------------------------------------------------------ */ public void saveSessions(boolean reactivate) throws Exception { @@ -491,7 +488,7 @@ public class HashSessionManager extends AbstractSessionManager { return; } - + if (!_storeDir.canWrite()) { __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable"); @@ -506,7 +503,7 @@ public class HashSessionManager extends AbstractSessionManager public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception { /* - * Take care of this class's fields first by calling + * Take care of this class's fields first by calling * defaultReadObject */ DataInputStream in = new DataInputStream(is); @@ -515,7 +512,7 @@ public class HashSessionManager extends AbstractSessionManager long created = in.readLong(); long accessed = in.readLong(); int requests = in.readInt(); - + if (session == null) session = (HashedSession)newSession(created, accessed, clusterId); session.setRequests(requests); @@ -536,7 +533,7 @@ public class HashSessionManager extends AbstractSessionManager return session; } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ protected class ClassLoadingObjectInputStream extends ObjectInputStream 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 fd60ea8addc..acfc47f5f3f 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 @@ -162,11 +162,8 @@ public class HashedSession extends AbstractSession // Access now to prevent race with idling period access(System.currentTimeMillis()); - if (LOG.isDebugEnabled()) - { LOG.debug("Deidling " + super.getId()); - } FileInputStream fis = null; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java index 8e73f3af1d0..5280023e7d1 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 @@ -18,6 +18,7 @@ import java.io.InputStream; import java.sql.Blob; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.Driver; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -52,10 +53,11 @@ import org.eclipse.jetty.util.log.Logger; */ public class JDBCSessionIdManager extends AbstractSessionIdManager { - final static Logger LOG = SessionHandler.__log; + final static Logger LOG = SessionHandler.LOG; protected final HashSet _sessionIds = new HashSet(); protected Server _server; + protected Driver _driver; protected String _driverClassName; protected String _connectionUrl; protected DataSource _datasource; @@ -106,7 +108,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager throws SQLException { _dbName = dbMeta.getDatabaseProductName().toLowerCase(); - LOG.debug ("Using database "+_dbName); + LOG.debug ("Using database {}",_dbName); _isLower = dbMeta.storesLowerCaseIdentifiers(); _isUpper = dbMeta.storesUpperCaseIdentifiers(); } @@ -184,6 +186,29 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager _connectionUrl=connectionUrl; } + /** + * Configure jdbc connection information via a jdbc Driver + * + * @param driverClass + * @param connectionUrl + */ + public void setDriverInfo (Driver driverClass, String connectionUrl) + { + _driver=driverClass; + _connectionUrl=connectionUrl; + } + + + public void setDatasource (DataSource ds) + { + _datasource = ds; + } + + public DataSource getDataSource () + { + return _datasource; + } + public String getDriverClassName() { return _driverClassName; @@ -230,7 +255,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager if ((System.currentTimeMillis()%2) == 0) _scavengeIntervalMs += tenPercent; - if (LOG.isDebugEnabled()) LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms"); + if (LOG.isDebugEnabled()) + LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms"); if (_timer!=null && (period!=old_period || _task==null)) { synchronized (this) @@ -409,7 +435,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager initializeDatabase(); prepareTables(); super.doStart(); - if (LOG.isDebugEnabled()) LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); + if (LOG.isDebugEnabled()) + LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); _timer=new Timer("JDBCSessionScavenger", true); setScavengeInterval(getScavengeInterval()); } @@ -456,12 +483,19 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager private void initializeDatabase () throws Exception { + if (_datasource != null) + return; //already set up + if (_jndiName!=null) { InitialContext ic = new InitialContext(); _datasource = (DataSource)ic.lookup(_jndiName); } - else if (_driverClassName!=null && _connectionUrl!=null) + else if ( _driver != null && _connectionUrl != null ) + { + DriverManager.registerDriver(_driver); + } + else if (_driverClassName != null && _connectionUrl != null) { Class.forName(_driverClassName); } @@ -652,7 +686,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager List expiredSessionIds = new ArrayList(); try { - if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); + if (LOG.isDebugEnabled()) + LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); if (_lastScavengeTime > 0) { connection = getConnection(); @@ -661,7 +696,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions); long lowerBound = (_lastScavengeTime - _scavengeIntervalMs); long upperBound = _lastScavengeTime; - if (LOG.isDebugEnabled()) LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound); + if (LOG.isDebugEnabled()) + LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound); statement.setLong(1, lowerBound); statement.setLong(2, upperBound); 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 0c15fb0df62..bcf443eb8d4 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 @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== @@ -44,9 +44,9 @@ import org.eclipse.jetty.util.log.Logger; * JDBCSessionManager * * SessionManager that persists sessions to a database to enable clustering. - * + * * Session data is persisted to the JettySessions table: - * + * * rowId (unique in cluster: webapp name/path + virtualhost + sessionId) * contextPath (of the context owning the session) * sessionId (unique in a context) @@ -58,7 +58,7 @@ import org.eclipse.jetty.util.log.Logger; * lastSavedTime (last time in milliseconds session access times were saved) * expiryTime (time in milliseconds that the session is due to expire) * map (attribute map) - * + * * As an optimization, to prevent thrashing the database, we do not persist * the accessTime and lastAccessTime every time the session is accessed. Rather, * we write it out every so often. The frequency is controlled by the saveIntervalSec @@ -67,18 +67,18 @@ import org.eclipse.jetty.util.log.Logger; public class JDBCSessionManager extends AbstractSessionManager { private static final Logger LOG = Log.getLogger(JDBCSessionManager.class); - - protected String __insertSession; - protected String __deleteSession; - protected String __selectSession; - protected String __updateSession; - protected String __updateSessionNode; + + protected String __insertSession; + protected String __deleteSession; + protected String __selectSession; + protected String __updateSession; + protected String __updateSessionNode; protected String __updateSessionAccessTime; protected String __sessionTableRowId; - + private ConcurrentHashMap _sessions; protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs - + /** * SessionData * @@ -108,7 +108,7 @@ public class JDBCSessionManager extends AbstractSessionManager _attributes = new HashMap(); _lastNode = getSessionIdManager().getWorkerName(); } - + public SessionData (String sessionId,Map attributes) { _id=sessionId; @@ -127,23 +127,23 @@ public class JDBCSessionManager extends AbstractSessionManager { return _created; } - + protected synchronized void setCreated (long ms) { _created = ms; } - + public synchronized long getAccessed () { return _accessed; } - + protected synchronized void setAccessed (long ms) { _accessed = ms; } - - + + public synchronized void setMaxIdleMs (long ms) { _maxIdleMs = ms; @@ -173,77 +173,77 @@ public class JDBCSessionManager extends AbstractSessionManager { return _cookieSet; } - + public synchronized void setRowId (String rowId) { _rowId=rowId; } - + protected synchronized String getRowId() { return _rowId; } - + protected synchronized Map getAttributeMap () { return _attributes; } - + protected synchronized void setAttributeMap (Map map) { _attributes = map; - } - + } + public synchronized void setLastNode (String node) { _lastNode=node; } - + public synchronized String getLastNode () { 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; + _expiryTime=time; } - + public synchronized long getExpiryTime () { return _expiryTime; } - + public synchronized void setVirtualHost (String vhost) { _virtualHost=vhost; } - + public synchronized String getVirtualHost () { return _virtualHost; } - + @Override public String toString () { @@ -254,8 +254,8 @@ public class JDBCSessionManager extends AbstractSessionManager } } - - + + /** * Session * @@ -269,12 +269,12 @@ public class JDBCSessionManager extends AbstractSessionManager /** * Session from a request. - * + * * @param request */ protected Session (HttpServletRequest request) { - super(JDBCSessionManager.this,request); + super(JDBCSessionManager.this,request); _data = new SessionData(getClusterId(),_jdbcAttributes); if (_dftMaxIdleSecs>0) _data.setMaxIdleMs(_dftMaxIdleSecs*1000); @@ -297,7 +297,7 @@ public class JDBCSessionManager extends AbstractSessionManager _jdbcAttributes.putAll(_data.getAttributeMap()); _data.setAttributeMap(_jdbcAttributes); } - + @Override public void setAttribute (String name, Object value) { @@ -308,20 +308,20 @@ public class JDBCSessionManager extends AbstractSessionManager @Override public void removeAttribute (String name) { - super.removeAttribute(name); + super.removeAttribute(name); _dirty=true; } - + @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) */ @Override @@ -339,7 +339,7 @@ public class JDBCSessionManager extends AbstractSessionManager return false; } - /** + /** * Exit from session * @see org.eclipse.jetty.server.session.AbstractSession#complete() */ @@ -350,7 +350,7 @@ public class JDBCSessionManager extends AbstractSessionManager try { if (_dirty) - { + { //The session attributes have changed, write to the db, ensuring //http passivation/activation listeners called willPassivate(); @@ -358,7 +358,7 @@ public class JDBCSessionManager extends AbstractSessionManager didActivate(); } else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000)) - { + { updateSessionAccessTime(_data); } } @@ -371,18 +371,19 @@ public class JDBCSessionManager extends AbstractSessionManager _dirty=false; } } - + @Override protected void timeout() throws IllegalStateException { - if (LOG.isDebugEnabled()) LOG.debug("Timing out session id="+getClusterId()); + if (LOG.isDebugEnabled()) + LOG.debug("Timing out session id="+getClusterId()); super.timeout(); } } - - - - + + + + /** * ClassLoadingObjectInputStream * @@ -413,46 +414,46 @@ public class JDBCSessionManager extends AbstractSessionManager } } } - - + + /** * Set the time in seconds which is the interval between * saving the session access time to the database. - * + * * This is an optimization that prevents the database from * being overloaded when a session is accessed very frequently. - * + * * On session exit, if the session attributes have NOT changed, * the time at which we last saved the accessed * time is compared to the current accessed time. If the interval * is at least saveIntervalSecs, then the access time will be * persisted to the database. - * + * * If any session attribute does change, then the attributes and * the accessed time are persisted. - * + * * @param sec */ public void setSaveInterval (long sec) { _saveIntervalSec=sec; } - + public long getSaveInterval () { return _saveIntervalSec; } - - + + /** * A method that can be implemented in subclasses to support * distributed caching of sessions. This method will be * called whenever the session is written to the database * because the session data has changed. - * + * * This could be used eg with a JMS backplane to notify nodes * that the session has changed and to delete the session from * the node's cache, and re-read it from the database. @@ -460,46 +461,46 @@ public class JDBCSessionManager extends AbstractSessionManager */ public void cacheInvalidate (Session session) { - + } - - - /** + + + /** * A session has been requested by it's 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) * but they CANNOT share the same contents. - * + * * Check if last node id is my node id, if so, then the session we have * in memory cannot be stale. If another node used the session last, then * we need to refresh from the db. - * - * NOTE: this method will go to the database, so if you only want to check + * + * NOTE: this method will go to the database, so if you only want to check * for the existence of a Session in memory, use _sessions.get(id) instead. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String) */ @Override public Session getSession(String idInCluster) { Session session = (Session)_sessions.get(idInCluster); - + synchronized (this) - { + { try - { - //check if we need to reload the session - + { + //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 + //to reduce the load on the database. This introduces a window of //possibility that the node may decide that the session is local to it, //when the session has actually been live on another node, and then //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. + //as load-balancers are generally well-behaved and consistently send + //sessions to the same node, changing only iff that node fails. SessionData data = null; long now = System.currentTimeMillis(); - if (LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) { if (session==null) LOG.debug("getSession("+idInCluster+"): not in session map,"+ @@ -515,9 +516,9 @@ public class JDBCSessionManager extends AbstractSessionManager " thisNode="+getSessionIdManager().getWorkerName()+ " difference="+(now - session._data._lastSaved)); } - + if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000))) - { + { 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)); } @@ -528,10 +529,10 @@ public class JDBCSessionManager extends AbstractSessionManager } else { - LOG.debug("getSession("+idInCluster+"): session in session map"); + LOG.debug("getSession("+idInCluster+"): session in session map"); data = session._data; } - + if (data != null) { if (!data.getLastNode().equals(getSessionIdManager().getWorkerName()) || session==null) @@ -551,7 +552,7 @@ public class JDBCSessionManager extends AbstractSessionManager } else if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): Session has expired"); - + } else if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): Session not stale "+session._data); @@ -563,7 +564,7 @@ public class JDBCSessionManager extends AbstractSessionManager session=null; if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): No session in database matching id="+idInCluster); } - + return session; } catch (Exception e) @@ -573,10 +574,10 @@ public class JDBCSessionManager extends AbstractSessionManager } } } - - /** + + /** * Get the number of sessions. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions() */ @Override @@ -591,9 +592,9 @@ public class JDBCSessionManager extends AbstractSessionManager } - /** + /** * Start the session manager. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart() */ @Override @@ -601,17 +602,17 @@ public class JDBCSessionManager extends AbstractSessionManager { if (_sessionIdManager==null) throw new IllegalStateException("No session id manager defined"); - + prepareTables(); - + _sessions = new ConcurrentHashMap(); super.doStart(); } - - - /** + + + /** * Stop the session manager. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop() */ @Override @@ -619,10 +620,10 @@ public class JDBCSessionManager extends AbstractSessionManager { _sessions.clear(); _sessions = null; - + super.doStop(); - } - + } + @Override protected void invalidateSessions() { @@ -634,10 +635,10 @@ public class JDBCSessionManager extends AbstractSessionManager //any other nodes } - + /** * Invalidate a session. - * + * * @param idInCluster */ protected void invalidateSession (String idInCluster) @@ -647,24 +648,24 @@ public class JDBCSessionManager extends AbstractSessionManager { session = (Session)_sessions.get(idInCluster); } - + if (session != null) { session.invalidate(); } } - - /** + + /** * Delete an existing session, both from the in-memory map and * the database. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String) */ @Override protected boolean removeSession(String idInCluster) { synchronized (this) - { + { Session session = (Session)_sessions.remove(idInCluster); try { @@ -680,9 +681,9 @@ public class JDBCSessionManager extends AbstractSessionManager } - /** + /** * Add a newly created session to our in-memory list for this node and persist it. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession) */ @Override @@ -695,7 +696,7 @@ public class JDBCSessionManager extends AbstractSessionManager { _sessions.put(session.getClusterId(), session); } - + //TODO or delay the store until exit out of session? If we crash before we store it //then session data will be lost. try @@ -711,9 +712,9 @@ public class JDBCSessionManager extends AbstractSessionManager } - /** + /** * Make a new Session. - * + * * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest) */ @Override @@ -721,9 +722,9 @@ public class JDBCSessionManager extends AbstractSessionManager { return new Session(request); } - + /* ------------------------------------------------------------ */ - /** Remove session from manager + /** Remove session from manager * @param session The session to remove * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and * {@link SessionIdManager#invalidateAll(String)} should be called. @@ -733,7 +734,7 @@ public class JDBCSessionManager extends AbstractSessionManager { // Remove session from context and global maps boolean removed = false; - + synchronized (this) { //take this session out of the map of sessions for this context @@ -748,10 +749,10 @@ public class JDBCSessionManager extends AbstractSessionManager { // Remove session from all context and global id maps _sessionIdManager.removeSession(session); - + if (invalidate) _sessionIdManager.invalidateAll(session.getClusterId()); - + if (invalidate && !_sessionListeners.isEmpty()) { HttpSessionEvent event=new HttpSessionEvent(session); @@ -764,16 +765,16 @@ public class JDBCSessionManager extends AbstractSessionManager } } } - - + + /** * Expire any Sessions we have in memory matching the list of * expired Session ids. - * + * * @param sessionIds */ protected void expire (List sessionIds) - { + { //don't attempt to scavenge if we are shutting down if (isStopping() || isStopped()) return; @@ -788,8 +789,9 @@ public class JDBCSessionManager extends AbstractSessionManager while (itor.hasNext()) { String sessionId = (String)itor.next(); - if (LOG.isDebugEnabled()) LOG.debug("Expiring session id "+sessionId); - + if (LOG.isDebugEnabled()) + LOG.debug("Expiring session id "+sessionId); + Session session = (Session)_sessions.get(sessionId); if (session != null) { @@ -798,28 +800,26 @@ public class JDBCSessionManager extends AbstractSessionManager } else { - if (LOG.isDebugEnabled()) LOG.debug("Unrecognized session id="+sessionId); + if (LOG.isDebugEnabled()) + LOG.debug("Unrecognized session id="+sessionId); } } } catch (Throwable t) { - if (t instanceof ThreadDeath) - throw ((ThreadDeath)t); - else - LOG.warn("Problem expiring sessions", t); + LOG.warn("Problem expiring sessions", t); } finally { thread.setContextClassLoader(old_loader); } } - - + + protected void prepareTables () { __sessionTableRowId = ((JDBCSessionIdManager)_sessionIdManager)._sessionTableRowId; - + __insertSession = "insert into "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ " ("+__sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+ " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; @@ -839,7 +839,7 @@ public class JDBCSessionManager extends AbstractSessionManager __updateSessionAccessTime = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+ " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+__sessionTableRowId+" = ?"; } - + /** * Load a session from the database * @param id @@ -860,7 +860,7 @@ public class JDBCSessionManager extends AbstractSessionManager Connection connection=null; PreparedStatement statement = null; try - { + { connection = getConnection(); statement = connection.prepareStatement(__selectSession); statement.setString(1, id); @@ -903,24 +903,24 @@ public class JDBCSessionManager extends AbstractSessionManager try { connection.close();} catch(Exception e) { LOG.warn(e); } } - } + } } }; - + if (_context==null) load.run(); else _context.getContextHandler().handle(load); - + if (_exception.get()!=null) throw _exception.get(); - + return _reference.get(); } - + /** * Insert a session into the database. - * + * * @param data * @throws Exception */ @@ -929,14 +929,14 @@ public class JDBCSessionManager extends AbstractSessionManager { if (data==null) return; - - //put into the database + + //put into the database Connection connection = getConnection(); PreparedStatement statement = null; try - { + { String rowId = calculateRowId(data); - + long now = System.currentTimeMillis(); connection.setAutoCommit(true); statement = connection.prepareStatement(__insertSession); @@ -951,34 +951,34 @@ public class JDBCSessionManager extends AbstractSessionManager statement.setLong(9, data.getCookieSet());//time cookie was set statement.setLong(10, now); //last saved time statement.setLong(11, data.getExpiryTime()); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(data.getAttributeMap()); byte[] bytes = baos.toByteArray(); - + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); statement.setBinaryStream(12, 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); - + if (LOG.isDebugEnabled()) LOG.debug("Stored session "+data); - } + } finally { if (connection!=null) connection.close(); } } - - + + /** * Update data on an existing persisted session. - * + * * @param data * @throws Exception */ @@ -987,30 +987,30 @@ public class JDBCSessionManager extends AbstractSessionManager { if (data==null) return; - + Connection connection = getConnection(); PreparedStatement statement = null; try - { + { long now = System.currentTimeMillis(); connection.setAutoCommit(true); - statement = connection.prepareStatement(__updateSession); + statement = connection.prepareStatement(__updateSession); statement.setString(1, getSessionIdManager().getWorkerName());//my node id statement.setLong(2, data.getAccessed());//accessTime statement.setLong(3, data.getLastAccessed()); //lastAccessTime statement.setLong(4, now); //last saved time statement.setLong(5, data.getExpiryTime()); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(data.getAttributeMap()); byte[] bytes = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - - statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob + + statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob statement.setString(7, data.getRowId()); //rowId statement.executeUpdate(); - + data.setLastSaved(now); if (LOG.isDebugEnabled()) LOG.debug("Updated session "+data); @@ -1021,11 +1021,11 @@ public class JDBCSessionManager extends AbstractSessionManager connection.close(); } } - - + + /** * Update the node on which the session was last seen to be my node. - * + * * @param data * @throws Exception */ @@ -1036,7 +1036,7 @@ public class JDBCSessionManager extends AbstractSessionManager Connection connection = getConnection(); PreparedStatement statement = null; try - { + { connection.setAutoCommit(true); statement = connection.prepareStatement(__updateSessionNode); statement.setString(1, nodeId); @@ -1052,10 +1052,10 @@ public class JDBCSessionManager extends AbstractSessionManager connection.close(); } } - + /** * Persist the time the session was last accessed. - * + * * @param data * @throws Exception */ @@ -1065,7 +1065,7 @@ public class JDBCSessionManager extends AbstractSessionManager Connection connection = getConnection(); PreparedStatement statement = null; try - { + { long now = System.currentTimeMillis(); connection.setAutoCommit(true); statement = connection.prepareStatement(__updateSessionAccessTime); @@ -1087,14 +1087,14 @@ public class JDBCSessionManager extends AbstractSessionManager connection.close(); } } - - - - + + + + /** * Delete a session from the database. Should only be called * when the session has been invalidated. - * + * * @param data * @throws Exception */ @@ -1116,11 +1116,11 @@ public class JDBCSessionManager extends AbstractSessionManager { if (connection!=null) connection.close(); - } + } } - - - + + + /** * Get a connection from the driver. * @return @@ -1128,13 +1128,13 @@ public class JDBCSessionManager extends AbstractSessionManager */ private Connection getConnection () throws SQLException - { + { return ((JDBCSessionIdManager)getSessionIdManager()).getConnection(); } /** * Calculate a unique id for this session across the cluster. - * + * * Unique id is composed of: contextpath_virtualhost0_sessionid * @param data * @return @@ -1146,31 +1146,31 @@ public class JDBCSessionManager extends AbstractSessionManager rowId = rowId+"_"+data.getId(); return rowId; } - + /** * Get the first virtual host for the context. - * + * * Used to help identify the exact session/contextPath. - * + * * @return 0.0.0.0 if no virtual host is defined */ private String getVirtualHost (ContextHandler.Context context) { String vhost = "0.0.0.0"; - + if (context==null) return vhost; - + String [] vhosts = context.getContextHandler().getVirtualHosts(); if (vhosts==null || vhosts.length==0 || vhosts[0]==null) return vhost; - + return vhosts[0]; } - + /** * Make an acceptable file name from a context path. - * + * * @param path * @return */ @@ -1178,7 +1178,7 @@ public class JDBCSessionManager extends AbstractSessionManager { if (path==null) return ""; - + return path.replace('/', '_').replace('.','_').replace('\\','_'); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java index 2e951710c73..a18a6e7553b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java @@ -36,7 +36,7 @@ import org.eclipse.jetty.util.log.Logger; */ public class SessionHandler extends ScopedHandler { - final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session"); + final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session"); /* -------------------------------------------------------------- */ private SessionManager _sessionManager; @@ -175,10 +175,10 @@ public class SessionHandler extends ScopedHandler } } - if(__log.isDebugEnabled()) + if(LOG.isDebugEnabled()) { - __log.debug("sessionManager="+_sessionManager); - __log.debug("session="+session); + LOG.debug("sessionManager="+_sessionManager); + LOG.debug("session="+session); } // start manual inline of nextScope(target,baseRequest,request,response); @@ -264,7 +264,8 @@ public class SessionHandler extends ScopedHandler { requested_session_id=cookies[i].getValue(); requested_session_id_from_cookie = true; - if(__log.isDebugEnabled())__log.debug("Got Session ID "+requested_session_id+" from cookie"); + if(LOG.isDebugEnabled()) + LOG.debug("Got Session ID {} from cookie",requested_session_id); session=sessionManager.getHttpSession(requested_session_id); if (session!=null && sessionManager.isValid(session)) @@ -297,8 +298,8 @@ public class SessionHandler extends ScopedHandler requested_session_id = uri.substring(s,i); requested_session_id_from_cookie = false; session=sessionManager.getHttpSession(requested_session_id); - if(__log.isDebugEnabled()) - __log.debug("Got Session ID "+requested_session_id+" from URL"); + if(LOG.isDebugEnabled()) + LOG.debug("Got Session ID {} from URL",requested_session_id); } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java index 1bf6b4680cf..fb04d57d61c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java @@ -9,8 +9,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManagerFactory; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.util.ssl.SslContextFactory; /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java index d60f82dea6b..1986e7bc08d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java @@ -14,29 +14,26 @@ package org.eclipse.jetty.server.ssl; import java.io.IOException; -import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpSchemes; -import org.eclipse.jetty.http.ssl.SslContextFactory; +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Buffers.Type; import org.eclipse.jetty.io.BuffersFactory; -import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.bio.SocketEndPoint; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; -import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; -import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SslConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.ssl.SslContextFactory; /* ------------------------------------------------------------ */ /** @@ -53,6 +50,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements public SslSelectChannelConnector() { this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH)); + setSoLingerTime(30000); } /* ------------------------------------------------------------ */ @@ -60,6 +58,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements { _sslContextFactory = sslContextFactory; setUseDirectBuffers(false); + setSoLingerTime(30000); } /* ------------------------------------------------------------ */ @@ -94,8 +93,8 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements request.setScheme(HttpSchemes.HTTPS); super.customize(endpoint,request); - SslSelectChannelEndPoint sslHttpChannelEndpoint=(SslSelectChannelEndPoint)endpoint; - SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine(); + SslConnection.SslEndPoint sslEndpoint=(SslConnection.SslEndPoint)endpoint; + SSLEngine sslEngine=sslEndpoint.getSslEngine(); SSLSession sslSession=sslEngine.getSession(); SslCertificates.customize(sslSession,endpoint,request); @@ -261,7 +260,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements @Deprecated public void setKeystore(String keystore) { - _sslContextFactory.setKeyStore(keystore); + _sslContextFactory.setKeyStorePath(keystore); } /* ------------------------------------------------------------ */ @@ -272,7 +271,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements @Deprecated public String getKeystore() { - return _sslContextFactory.getKeyStore(); + return _sslContextFactory.getKeyStorePath(); } /* ------------------------------------------------------------ */ @@ -538,21 +537,31 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements /* ------------------------------------------------------------------------------- */ @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint) { - SSLEngine engine = createSSLEngine(channel); - SslSelectChannelEndPoint endp = new SslSelectChannelEndPoint(_sslBuffers,channel,selectSet,key,engine, SslSelectChannelConnector.this._maxIdleTime); - endp.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate()); - return endp; + try + { + SSLEngine engine = createSSLEngine(channel); + SslConnection connection = newSslConnection(endpoint, engine); + AsyncConnection delegate = newPlainConnection(channel, connection.getSslEndPoint()); + connection.getSslEndPoint().setConnection(delegate); + connection.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate()); + return connection; + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } } - /* ------------------------------------------------------------------------------- */ - @Override - protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint) { - HttpConnection connection=(HttpConnection)super.newConnection(channel,endpoint); - ((HttpParser)connection.getParser()).setForceContentBuffer(true); - return connection; + return super.newConnection(channel, endPoint); + } + + protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine) + { + return new SslConnection(engine, endpoint); } /* ------------------------------------------------------------ */ @@ -565,33 +574,19 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException { SSLEngine engine; - if (channel != null && _sslContextFactory.isSessionCachingEnabled()) + if (channel != null) { String peerHost = channel.socket().getInetAddress().getHostAddress(); int peerPort = channel.socket().getPort(); - engine = _sslContextFactory.getSslContext().createSSLEngine(peerHost, peerPort); + engine = _sslContextFactory.newSslEngine(peerHost, peerPort); } else { - engine = _sslContextFactory.getSslContext().createSSLEngine(); + engine = _sslContextFactory.newSslEngine(); } - customizeEngine(engine); - return engine; - } - /* ------------------------------------------------------------ */ - private void customizeEngine(SSLEngine engine) - { engine.setUseClientMode(false); - - if (_sslContextFactory.getWantClientAuth()) - engine.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - if (_sslContextFactory.getNeedClientAuth()) - engine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - engine.setEnabledCipherSuites( - _sslContextFactory.selectCipherSuites(engine.getEnabledCipherSuites(), - engine.getSupportedCipherSuites())); + return engine; } /* ------------------------------------------------------------ */ @@ -601,22 +596,13 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements @Override protected void doStart() throws Exception { - if (!_sslContextFactory.checkConfig()) - { - throw new IllegalStateException("SSL context is not configured correctly."); - } + _sslContextFactory.checkKeyStore(); _sslContextFactory.start(); - SSLEngine sslEngine = _sslContextFactory.getSslContext().createSSLEngine(); + SSLEngine sslEngine = _sslContextFactory.newSslEngine(); sslEngine.setUseClientMode(false); - sslEngine.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - sslEngine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - sslEngine.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites( - sslEngine.getEnabledCipherSuites(), - sslEngine.getSupportedCipherSuites())); SSLSession sslSession = sslEngine.getSession(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java index 1783f9aba0e..f18981cce7b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java @@ -4,17 +4,16 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server.ssl; import java.io.IOException; -import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; @@ -23,33 +22,33 @@ import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.eclipse.jetty.http.HttpSchemes; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.bio.SocketEndPoint; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.bio.SocketConnector; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; /* ------------------------------------------------------------ */ /** * SSL Socket Connector. - * + * * This specialization of SocketConnector is an abstract listener that can be used as the basis for a * specific JSSE listener. - * - * The original of this class was heavily based on the work from Court Demas, which in turn is + * + * The original of this class was heavily based on the work from Court Demas, which in turn is * based on the work from Forge Research. Since JSSE, this class has evolved significantly from * that early work. - * + * * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector" * - * + * */ public class SslSocketConnector extends SocketConnector implements SslConnector { @@ -65,8 +64,10 @@ public class SslSocketConnector extends SocketConnector implements SslConnector public SslSocketConnector() { this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH)); + setSoLingerTime(30000); } + /* ------------------------------------------------------------ */ public SslSocketConnector(SslContextFactory sslContextFactory) { _sslContextFactory = sslContextFactory; @@ -85,7 +86,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /** * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered * a vulnerability in SSL/TLS with re-negotiation. If your JVM - * does not have CVE-2009-3555 fixed, then re-negotiation should + * does not have CVE-2009-3555 fixed, then re-negotiation should * not be allowed. * @param allowRenegotiate true if re-negotiation is allowed (default false) */ @@ -98,19 +99,19 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override public void accept(int acceptorID) throws IOException, InterruptedException - { + { Socket socket = _serverSocket.accept(); configure(socket); - + ConnectorEndPoint connection=new SslConnectorEndPoint(socket); connection.dispatch(); } - + /* ------------------------------------------------------------ */ @Override protected void configure(Socket socket) throws IOException - { + { super.configure(socket); } @@ -129,8 +130,8 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * certificate in the chain is the one set by the client, the next is the one used to * authenticate the first, and so on. * - * - * @param endpoint The Socket the request arrived on. + * + * @param endpoint The Socket the request arrived on. * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}. * @param request HttpRequest to be customised. */ @@ -140,7 +141,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { super.customize(endpoint, request); request.setScheme(HttpSchemes.HTTPS); - + SocketEndPoint socket_end_point = (SocketEndPoint)endpoint; SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport(); SSLSession sslSession = sslSocket.getSession(); @@ -148,7 +149,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector SslCertificates.customize(sslSession,endpoint,request); } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites() * @deprecated @@ -157,7 +158,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector public String[] getExcludeCipherSuites() { return _sslContextFactory.getExcludeCipherSuites(); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#getIncludeCipherSuites() @@ -177,7 +178,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Deprecated public String getKeystore() { - return _sslContextFactory.getKeyStore(); + return _sslContextFactory.getKeyStorePath(); } /* ------------------------------------------------------------ */ @@ -186,7 +187,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getKeystoreType() + public String getKeystoreType() { return _sslContextFactory.getKeyStoreType(); } @@ -208,7 +209,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getProtocol() + public String getProtocol() { return _sslContextFactory.getProtocol(); } @@ -229,7 +230,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSecureRandomAlgorithm() + public String getSecureRandomAlgorithm() { return _sslContextFactory.getSecureRandomAlgorithm(); } @@ -240,7 +241,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSslKeyManagerFactoryAlgorithm() + public String getSslKeyManagerFactoryAlgorithm() { return _sslContextFactory.getSslKeyManagerFactoryAlgorithm(); } @@ -251,7 +252,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSslTrustManagerFactoryAlgorithm() + public String getSslTrustManagerFactoryAlgorithm() { return _sslContextFactory.getTrustManagerFactoryAlgorithm(); } @@ -313,7 +314,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector final int confidentialPort = getConfidentialPort(); return confidentialPort == 0 || confidentialPort == request.getServerPort(); } - + /* ------------------------------------------------------------ */ /** * By default, we're integral, given we speak SSL. But, if we've been told about an integral @@ -329,6 +330,22 @@ public class SslSocketConnector extends SocketConnector implements SslConnector return integralPort == 0 || integralPort == request.getServerPort(); } + /* ------------------------------------------------------------ */ + @Override + public void open() throws IOException + { + _sslContextFactory.checkKeyStore(); + try + { + _sslContextFactory.start(); + } + catch(Exception e) + { + throw new RuntimeIOException(e); + } + super.open(); + } + /* ------------------------------------------------------------ */ /** * {@inheritDoc} @@ -336,16 +353,12 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override protected void doStart() throws Exception { - if (!_sslContextFactory.checkConfig()) - { - throw new IllegalStateException("SSL context is not configured correctly."); - } - + _sslContextFactory.checkKeyStore(); _sslContextFactory.start(); - + super.doStart(); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.bio.SocketConnector#doStop() @@ -357,14 +370,14 @@ public class SslSocketConnector extends SocketConnector implements SslConnector super.doStop(); } - + /* ------------------------------------------------------------ */ /** * @param host The host name that this server should listen on - * @param port the port that this server should listen on + * @param port the port that this server should listen on * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)} * @return A new {@link ServerSocket socket object} bound to the supplied address with all other - * settings as per the current configuration of this connector. + * settings as per the current configuration of this connector. * @see #setWantClientAuth(boolean) * @see #setNeedClientAuth(boolean) * @exception IOException @@ -372,22 +385,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException { - SSLServerSocketFactory factory = _sslContextFactory.getSslContext().getServerSocketFactory(); - - SSLServerSocket socket = - (SSLServerSocket) (host==null ? - factory.createServerSocket(port,backlog): - factory.createServerSocket(port,backlog,InetAddress.getByName(host))); - - if (_sslContextFactory.getWantClientAuth()) - socket.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - if (_sslContextFactory.getNeedClientAuth()) - socket.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - socket.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites( - socket.getEnabledCipherSuites(), - socket.getSupportedCipherSuites())); - return socket; + return _sslContextFactory.newSslServerSocket(host,port,backlog); } /* ------------------------------------------------------------ */ @@ -431,7 +429,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Deprecated public void setKeystore(String keystore) { - _sslContextFactory.setKeyStore(keystore); + _sslContextFactory.setKeyStorePath(keystore); } /* ------------------------------------------------------------ */ @@ -440,7 +438,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setKeystoreType(String keystoreType) + public void setKeystoreType(String keystoreType) { _sslContextFactory.setKeyStoreType(keystoreType); } @@ -448,7 +446,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** * Set the value of the needClientAuth property - * + * * @param needClientAuth true iff we require client certificate authentication. * @deprecated */ @@ -457,7 +455,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setNeedClientAuth(needClientAuth); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String) @@ -468,7 +466,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setKeyStorePassword(password); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String) @@ -486,7 +484,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setProtocol(String protocol) + public void setProtocol(String protocol) { _sslContextFactory.setProtocol(protocol); } @@ -507,7 +505,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setSecureRandomAlgorithm(String algorithm) + public void setSecureRandomAlgorithm(String algorithm) { _sslContextFactory.setSecureRandomAlgorithm(algorithm); } @@ -518,18 +516,18 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setSslKeyManagerFactoryAlgorithm(String algorithm) + public void setSslKeyManagerFactoryAlgorithm(String algorithm) { _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String) * @deprecated */ @Deprecated - public void setSslTrustManagerFactoryAlgorithm(String algorithm) + public void setSslTrustManagerFactoryAlgorithm(String algorithm) { _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm); } @@ -544,7 +542,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setTrustStore(truststore); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String) @@ -555,7 +553,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setTrustStoreType(truststoreType); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) @@ -580,9 +578,9 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** - * Set the value of the _wantClientAuth property. This property is used + * Set the value of the _wantClientAuth property. This property is used * internally when opening server sockets. - * + * * @param wantClientAuth true if we want client certificate authentication. * @see SSLServerSocket#setWantClientAuth * @deprecated @@ -603,7 +601,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _handshakeTimeout = msec; } - + /* ------------------------------------------------------------ */ public int getHandshakeTimeout () @@ -618,19 +616,19 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { super(socket); } - + @Override public void shutdownOutput() throws IOException { close(); } - + @Override public void shutdownInput() throws IOException { close(); } - + @Override public void run() { @@ -638,7 +636,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { int handshakeTimeout = getHandshakeTimeout(); int oldTimeout = _socket.getSoTimeout(); - if (handshakeTimeout > 0) + if (handshakeTimeout > 0) _socket.setSoTimeout(handshakeTimeout); final SSLSocket ssl=(SSLSocket)_socket; @@ -668,7 +666,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector } catch (SSLException e) { - LOG.debug(e); + LOG.debug(e); try{close();} catch(IOException e2){LOG.ignore(e2);} } @@ -677,14 +675,14 @@ public class SslSocketConnector extends SocketConnector implements SslConnector LOG.debug(e); try{close();} catch(IOException e2){LOG.ignore(e2);} - } + } } } /* ------------------------------------------------------------ */ /** * Unsupported. - * + * * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) * @deprecated */ @@ -697,7 +695,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** * Unsupported. - * + * * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) * @deprecated */ diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java index f006313c14a..7c35f79d147 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java @@ -13,6 +13,9 @@ package org.eclipse.jetty.server; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -20,6 +23,7 @@ import java.io.PrintWriter; import java.net.Socket; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -35,9 +39,6 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class AbstractConnectorTest { private static final Logger LOG = Log.getLogger(AbstractConnectorTest.class); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java deleted file mode 100644 index c0b85eb0a2c..00000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncContextTest.java +++ /dev/null @@ -1,296 +0,0 @@ -// ======================================================================== -// Copyright (c) 2004-2009 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.assertTrue; - -import java.io.IOException; -import java.io.InputStream; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationListener; -import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.server.session.SessionHandler; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class AsyncContextTest -{ - protected Server _server = new Server(); - protected SuspendHandler _handler = new SuspendHandler(); - protected LocalConnector _connector; - - @Before - public void init() throws Exception - { - _connector = new LocalConnector(); - _server.setConnectors(new Connector[]{ _connector }); - - SessionHandler session = new SessionHandler(); - session.setHandler(_handler); - - _server.setHandler(session); - _server.start(); - } - - @After - public void destroy() throws Exception - { - _server.stop(); - _server.join(); - } - - @Test - public void testSuspendResume() throws Exception - { - _handler.setRead(0); - _handler.setSuspendFor(1000); - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(-1); - check("TIMEOUT",process(null)); - - _handler.setSuspendFor(10000); - - _handler.setResumeAfter(0); - _handler.setCompleteAfter(-1); - check("RESUMED",process(null)); - - _handler.setResumeAfter(100); - _handler.setCompleteAfter(-1); - check("RESUMED",process(null)); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(0); - check("COMPLETED",process(null)); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(200); - check("COMPLETED",process(null)); - - _handler.setRead(-1); - - _handler.setResumeAfter(0); - _handler.setCompleteAfter(-1); - check("RESUMED",process("wibble")); - - _handler.setResumeAfter(100); - _handler.setCompleteAfter(-1); - check("RESUMED",process("wibble")); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(0); - check("COMPLETED",process("wibble")); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(100); - check("COMPLETED",process("wibble")); - - _handler.setRead(6); - - _handler.setResumeAfter(0); - _handler.setCompleteAfter(-1); - check("RESUMED",process("wibble")); - - _handler.setResumeAfter(100); - _handler.setCompleteAfter(-1); - check("RESUMED",process("wibble")); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(0); - check("COMPLETED",process("wibble")); - - _handler.setResumeAfter(-1); - _handler.setCompleteAfter(100); - check("COMPLETED",process("wibble")); - } - - protected void check(String content,String response) - { - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); - assertTrue(response.contains(content)); - } - - private synchronized String process(String content) throws Exception - { - String request = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n"; - - if (content==null) - request+="\r\n"; - else - request+="Content-Length: "+content.length()+"\r\n" + "\r\n" + content; - - return _connector.getResponses(request); - } - - private static class SuspendHandler extends HandlerWrapper - { - private int _read; - private long _suspendFor=-1; - private long _resumeAfter=-1; - private long _completeAfter=-1; - - public SuspendHandler() - { - } - - public int getRead() - { - return _read; - } - - public void setRead(int read) - { - _read = read; - } - - public long getSuspendFor() - { - return _suspendFor; - } - - public void setSuspendFor(long suspendFor) - { - _suspendFor = suspendFor; - } - - public long getResumeAfter() - { - return _resumeAfter; - } - - public void setResumeAfter(long resumeAfter) - { - _resumeAfter = resumeAfter; - } - - public long getCompleteAfter() - { - return _completeAfter; - } - - public void setCompleteAfter(long completeAfter) - { - _completeAfter = completeAfter; - } - - @Override - public void handle(String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException - { - if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) - { - if (_read>0) - { - byte[] buf=new byte[_read]; - request.getInputStream().read(buf); - } - else if (_read<0) - { - InputStream in = request.getInputStream(); - int b=in.read(); - while(b!=-1) - b=in.read(); - } - - final AsyncContext asyncContext = baseRequest.startAsync(); - asyncContext.addContinuationListener(__asyncListener); - if (_suspendFor>0) - asyncContext.setTimeout(_suspendFor); - - if (_completeAfter>0) - { - new Thread() { - @Override - public void run() - { - try - { - Thread.sleep(_completeAfter); - response.getOutputStream().print("COMPLETED"); - response.setStatus(200); - baseRequest.setHandled(true); - asyncContext.complete(); - } - catch(Exception e) - { - e.printStackTrace(); - } - } - }.start(); - } - else if (_completeAfter==0) - { - response.getOutputStream().print("COMPLETED"); - response.setStatus(200); - baseRequest.setHandled(true); - asyncContext.complete(); - } - - if (_resumeAfter>0) - { - new Thread() { - @Override - public void run() - { - try - { - Thread.sleep(_resumeAfter); - if(((HttpServletRequest)asyncContext.getRequest()).getSession(true).getId()!=null) - asyncContext.dispatch(); - } - catch(Exception e) - { - e.printStackTrace(); - } - } - }.start(); - } - else if (_resumeAfter==0) - { - asyncContext.dispatch(); - } - } - else if (request.getAttribute("TIMEOUT")!=null) - { - response.setStatus(200); - response.getOutputStream().print("TIMEOUT"); - baseRequest.setHandled(true); - } - else - { - response.setStatus(200); - response.getOutputStream().print("RESUMED"); - baseRequest.setHandled(true); - } - } - } - - private static ContinuationListener __asyncListener = new ContinuationListener() - { - public void onComplete(Continuation continuation) - { - } - - public void onTimeout(Continuation continuation) - { - continuation.setAttribute("TIMEOUT",Boolean.TRUE); - continuation.resume(); - } - }; -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncUploadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java similarity index 65% rename from jetty-server/src/test/java/org/eclipse/jetty/server/AsyncUploadTest.java rename to jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java index d970ca15bdd..e63f875a9ef 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncUploadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Arrays; +import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; @@ -33,6 +34,7 @@ import org.eclipse.jetty.continuation.ContinuationSupport; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -40,17 +42,18 @@ import org.junit.Test; /** * @version $Revision: 889 $ $Date: 2009-09-14 14:52:16 +1000 (Mon, 14 Sep 2009) $ */ -public class AsyncUploadTest +public class AsyncRequestReadTest { private static Server server; private static Connector connector; - private static int total; + private final static Exchanger __total=new Exchanger(); @BeforeClass public static void startServer() throws Exception { server = new Server(); connector = new SelectChannelConnector(); + connector.setMaxIdleTime(10000); server.addConnector(connector); server.setHandler(new EmptyHandler()); server.start(); @@ -62,7 +65,7 @@ public class AsyncUploadTest server.stop(); server.join(); } - + @Test public void test() throws Exception { @@ -71,14 +74,17 @@ public class AsyncUploadTest byte[] content = new byte[16*4096]; Arrays.fill(content, (byte)120); - long start = System.nanoTime(); OutputStream out = socket.getOutputStream(); - out.write("POST / HTTP/1.1\r\n".getBytes()); - out.write("Host: localhost\r\n".getBytes()); - out.write(("Content-Length: "+content.length+"\r\n").getBytes()); - out.write("Content-Type: bytes\r\n".getBytes()); - out.write("Connection: close\r\n".getBytes()); - out.write("\r\n".getBytes()); + String header= + "POST / HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Content-Length: "+content.length+"\r\n"+ + "Content-Type: bytes\r\n"+ + "Connection: close\r\n"+ + "\r\n"; + byte[] h=header.getBytes(StringUtil.__ISO_8859_1); + + out.write(h); out.flush(); out.write(content,0,4*4096); @@ -91,14 +97,63 @@ public class AsyncUploadTest InputStream in = socket.getInputStream(); String response = IO.toString(in); - // System.err.println(response); assertTrue(response.indexOf("200 OK")>0); - long end = System.nanoTime(); - System.err.println("upload time: " + TimeUnit.NANOSECONDS.toMillis(end - start)); + long total=__total.exchange(0L,30,TimeUnit.SECONDS); assertEquals(content.length, total); } + + @Test + public void tests() throws Exception + { + runTest(64,4,4,20); + runTest(256,16,16,50); + runTest(256,1,128,10); + runTest(128*1024,1,64,10); + runTest(256*1024,5321,10,100); + runTest(512*1024,32*1024,10,10); + } + + + public void runTest(int contentSize, int chunkSize, int chunks, int delayMS) throws Exception + { + String tst=contentSize+","+chunkSize+","+chunks+","+delayMS; + //System.err.println(tst); + + final Socket socket = new Socket("localhost",connector.getLocalPort()); + byte[] content = new byte[contentSize]; + Arrays.fill(content, (byte)120); + + OutputStream out = socket.getOutputStream(); + out.write("POST / HTTP/1.1\r\n".getBytes()); + out.write("Host: localhost\r\n".getBytes()); + out.write(("Content-Length: "+content.length+"\r\n").getBytes()); + out.write("Content-Type: bytes\r\n".getBytes()); + out.write("Connection: close\r\n".getBytes()); + out.write("\r\n".getBytes()); + out.flush(); + + int offset=0; + for (int i=0;i0); + + long total=__total.exchange(0L,30,TimeUnit.SECONDS); + assertEquals(tst,content.length, total); + } + + private static class EmptyHandler extends AbstractHandler { public void handle(String path, final Request request, HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws IOException, ServletException @@ -112,15 +167,14 @@ public class AsyncUploadTest @Override public void run() { + long total=0; try { - Thread.sleep(100); InputStream in = request.getInputStream(); byte[] b = new byte[4*4096]; int read; while((read =in.read(b))>=0) total += read; - System.err.println("Read "+ total); } catch(Exception e) { @@ -131,6 +185,14 @@ public class AsyncUploadTest { httpResponse.setStatus(200); continuation.complete(); + try + { + __total.exchange(total); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } } } }.start(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java deleted file mode 100644 index 5dde4771887..00000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -// ======================================================================== -// Copyright (c) 2004-2009 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.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; - -import org.eclipse.jetty.io.Buffer; -import org.eclipse.jetty.io.View; -import org.eclipse.jetty.io.nio.IndirectNIOBuffer; -import org.eclipse.jetty.io.nio.NIOBuffer; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; -import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; -import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.junit.BeforeClass; - -/** - * HttpServer Tester. - */ -public class BusySelectChannelServerTest extends HttpServerTestBase -{ - @BeforeClass - public static void init() throws Exception - { - SelectChannelConnector connector=new SelectChannelConnector() - { - @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException - { - return new SelectChannelEndPoint(channel,selectSet,key, _maxIdleTime) - { - int write; - int read; - - /* ------------------------------------------------------------ */ - /* (non-Javadoc) - * @see org.eclipse.io.nio.SelectChannelEndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) - */ - @Override - public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException - { - int x=write++&0xff; - if (x<8) - return 0; - if (x<32) - return flush(header); - return super.flush(header,buffer,trailer); - } - - /* ------------------------------------------------------------ */ - /* (non-Javadoc) - * @see org.eclipse.io.nio.SelectChannelEndPoint#flush(org.eclipse.io.Buffer) - */ - @Override - public int flush(Buffer buffer) throws IOException - { - int x=write++&0xff; - if (x<8) - return 0; - if (x<32) - { - View v = new View(buffer); - v.setPutIndex(v.getIndex()+1); - int l=super.flush(v); - if (l>0) - buffer.skip(l); - return l; - } - return super.flush(buffer); - } - - /* ------------------------------------------------------------ */ - /* (non-Javadoc) - * @see org.eclipse.io.nio.ChannelEndPoint#fill(org.eclipse.io.Buffer) - */ - @Override - public int fill(Buffer buffer) throws IOException - { - int x=read++&0xff; - if (x<8) - return 0; - - if (x<16 && buffer.space()>=1) - { - NIOBuffer one = new IndirectNIOBuffer(1); - int l=super.fill(one); - if (l>0) - buffer.put(one.peek(0)); - return l; - } - - if (x<24 && buffer.space()>=2) - { - NIOBuffer two = new IndirectNIOBuffer(2); - int l=super.fill(two); - if (l>0) - buffer.put(two.peek(0)); - if (l>1) - buffer.put(two.peek(1)); - return l; - } - - if (x<64 && buffer.space()>=3) - { - NIOBuffer three = new IndirectNIOBuffer(3); - int l=super.fill(three); - if (l>0) - buffer.put(three.peek(0)); - if (l>1) - buffer.put(three.peek(1)); - if (l>2) - buffer.put(three.peek(2)); - return l; - } - - return super.fill(buffer); - } - }; - } - }; - connector.setAcceptors(1); - startServer(connector); - } - - @Override - public void testAvailable() throws Exception - { - } - - @Override - public void testBlockingWhileWritingResponseContent() throws Exception - { - // TODO work out why this one fails - } - - -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java index 26caaa934b5..f01dfc831a8 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java @@ -13,49 +13,51 @@ package org.eclipse.jetty.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; - +import java.net.SocketException; +import java.util.concurrent.Exchanger; +import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import junit.framework.Assert; - +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.SslConnection; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; +import org.junit.Assert; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.matchers.JUnitMatchers.containsString; + public abstract class ConnectorTimeoutTest extends HttpServerTestFixture { protected static final int MAX_IDLE_TIME=250; - + static { System.setProperty("org.eclipse.jetty.io.nio.IDLE_TICK","100"); } - - + + @Test public void testMaxIdleWithRequest10() throws Exception - { + { configureServer(new HelloWorldHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); - + OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); - String content="Wibble"; - byte[] contentB=content.getBytes("utf-8"); os.write(( "GET / HTTP/1.0\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ @@ -64,8 +66,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture os.flush(); long start = System.currentTimeMillis(); - String in = IO.toString(is); - + IO.toString(is); + Thread.sleep(300); assertEquals(-1, is.read()); @@ -75,13 +77,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithRequest11() throws Exception - { + { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); - + OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); @@ -97,25 +99,173 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture os.flush(); long start = System.currentTimeMillis(); - String in = IO.toString(is); - + IO.toString(is); + Thread.sleep(300); assertEquals(-1, is.read()); Assert.assertTrue(System.currentTimeMillis()-start>200); Assert.assertTrue(System.currentTimeMillis()-start<5000); } - + + @Test + public void testMaxIdleWithRequest10NoClientClose() throws Exception + { + final Exchanger endpoint = new Exchanger(); + configureServer(new HelloWorldHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + try + { + endpoint.exchange(baseRequest.getConnection().getEndPoint()); + } + catch(Exception e) + {} + super.handle(target,baseRequest,request,response); + } + + }); + Socket client=newSocket(HOST,_connector.getLocalPort()); + client.setSoTimeout(10000); + + assertFalse(client.isClosed()); + + OutputStream os=client.getOutputStream(); + InputStream is=client.getInputStream(); + + os.write(( + "GET / HTTP/1.0\r\n"+ + "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ + "connection: close\r\n"+ + "\r\n").getBytes("utf-8")); + os.flush(); + + // Get the server side endpoint + EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS); + if (endp instanceof SslConnection.SslEndPoint) + endp=((SslConnection.SslEndPoint)endp).getEndpoint(); + + // read the response + String result=IO.toString(is); + Assert.assertThat("OK",result,containsString("200 OK")); + + // check client reads EOF + assertEquals(-1, is.read()); + + // wait for idle timeout + TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2); + + + // further writes will get broken pipe or similar + try + { + for (int i=0;i<1000;i++) + { + os.write(( + "GET / HTTP/1.0\r\n"+ + "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ + "connection: keep-alive\r\n"+ + "\r\n").getBytes("utf-8")); + os.flush(); + } + Assert.fail("half close should have timed out"); + } + catch(SocketException e) + { + // expected + } + // check the server side is closed + Assert.assertFalse(endp.isOpen()); + } + + @Test + public void testMaxIdleWithRequest11NoClientClose() throws Exception + { + final Exchanger endpoint = new Exchanger(); + configureServer(new EchoHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, + ServletException + { + try + { + endpoint.exchange(baseRequest.getConnection().getEndPoint()); + } + catch(Exception e) + {} + super.handle(target,baseRequest,request,response); + } + + }); + Socket client=newSocket(HOST,_connector.getLocalPort()); + client.setSoTimeout(10000); + + assertFalse(client.isClosed()); + + OutputStream os=client.getOutputStream(); + InputStream is=client.getInputStream(); + + String content="Wibble"; + byte[] contentB=content.getBytes("utf-8"); + os.write(( + "POST /echo HTTP/1.1\r\n"+ + "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ + "content-type: text/plain; charset=utf-8\r\n"+ + "content-length: "+contentB.length+"\r\n"+ + "connection: close\r\n"+ + "\r\n").getBytes("utf-8")); + os.write(contentB); + os.flush(); + + // Get the server side endpoint + EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS); + + // read the response + IO.toString(is); + + // check client reads EOF + assertEquals(-1, is.read()); + + TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2); + + + // further writes will get broken pipe or similar + try + { + for (int i=0;i<1000;i++) + { + os.write(( + "GET / HTTP/1.0\r\n"+ + "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ + "connection: keep-alive\r\n"+ + "\r\n").getBytes("utf-8")); + os.flush(); + } + Assert.fail("half close should have timed out"); + } + catch(SocketException e) + { + // expected + } + + // check the server side is closed + Assert.assertFalse(endp.isOpen()); + } + @Test public void testMaxIdleNoRequest() throws Exception - { + { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); InputStream is=client.getInputStream(); assertFalse(client.isClosed()); - + Thread.sleep(500); long start = System.currentTimeMillis(); try @@ -125,25 +275,25 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture } catch(SSLException e) { - + } catch(Exception e) { e.printStackTrace(); } Assert.assertTrue(System.currentTimeMillis()-start<5000); - - } + + } @Test public void testMaxIdleWithSlowRequest() throws Exception - { + { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); - + OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); @@ -165,7 +315,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture os.write(contentB); os.flush(); } - + String in = IO.toString(is); int offset=0; for (int i =0;i<20;i++) @@ -177,13 +327,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithSlowResponse() throws Exception - { + { configureServer(new SlowResponseHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); - + OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); @@ -194,7 +344,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture "Connection: close\r\n"+ "\r\n").getBytes("utf-8")); os.flush(); - + String in = IO.toString(is); int offset=0; for (int i =0;i<20;i++) @@ -206,13 +356,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithWait() throws Exception - { + { configureServer(new WaitHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); - + OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); @@ -223,12 +373,12 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture "Connection: close\r\n"+ "\r\n").getBytes("utf-8")); os.flush(); - + String in = IO.toString(is); int offset=in.indexOf("Hello World"); Assert.assertTrue(offset>0); } - + protected static class SlowResponseHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -236,7 +386,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture baseRequest.setHandled(true); response.setStatus(200); OutputStream out = response.getOutputStream(); - + for (int i=0;i<20;i++) { out.write("Hello World\r\n".getBytes()); @@ -246,7 +396,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture out.close(); } } - + protected static class WaitHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java index adaf8751b28..301c352899a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java @@ -151,8 +151,7 @@ public class DumpHandler extends AbstractHandler String val=request.getParameter("CookieVal"); val=val.replaceAll("[ \n\r=<>]","?"); Cookie cookie= - new Cookie(cookie_name.trim(), - request.getParameter("CookieVal")); + new Cookie(cookie_name.trim(),val); if ("Clear Cookie".equals(cookie_action)) cookie.setMaxAge(0); response.addCookie(cookie); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java index f5afdb662be..b73c3fdbc5c 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java @@ -28,7 +28,7 @@ public class EncodedHttpURITest { String url = "http://www.foo.com/ma\u00F1ana"; byte[] asISO = url.getBytes("ISO-8859-1"); - String str = new String(asISO, "ISO-8859-1"); + new String(asISO, "ISO-8859-1"); //use a non UTF-8 charset as the encoding and url-escape as per //http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java index 55cecada02e..67207366582 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java @@ -33,7 +33,6 @@ import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StdErrLog; import org.junit.After; import org.junit.Before; @@ -124,7 +123,7 @@ public class HttpConnectionTest int offset=0; offset = checkContains(response,offset,"HTTP/1.1 200"); - offset = checkContains(response,offset,"/R1"); + checkContains(response,offset,"/R1"); } @Test @@ -155,7 +154,7 @@ public class HttpConnectionTest { try { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true); String response; @@ -177,7 +176,7 @@ public class HttpConnectionTest response=connector.getResponses("GET /foo/bar%c0%00 HTTP/1.1\n"+ "Host: localhost\n"+ "\015\012"); - checkContains(response,0,"pathInfo=/foo/bar?"); + checkContains(response,0,"HTTP/1.1 400"); response=connector.getResponses("GET /bad/utf8%c1 HTTP/1.1\n"+ "Host: localhost\n"+ @@ -186,7 +185,7 @@ public class HttpConnectionTest } finally { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(false); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false); } } @@ -336,7 +335,7 @@ public class HttpConnectionTest Logger logger=null; try { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true); response=connector.getResponses(requests); offset = checkContains(response,offset,"HTTP/1.1 500"); offset = checkContains(response,offset,"Connection: close"); @@ -344,7 +343,7 @@ public class HttpConnectionTest } finally { - ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(false); + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false); } } @@ -366,7 +365,7 @@ public class HttpConnectionTest "5;\015\012"+ "12345\015\012"+ "0;\015\012\015\012"); - offset = checkContains(response,offset,"Connection: close"); + checkContains(response,offset,"Connection: close"); } catch (Exception e) { @@ -395,7 +394,7 @@ public class HttpConnectionTest "Cookie: "+cookie+"\n"+ "\015\012" ); - offset = checkContains(response, offset, "HTTP/1.1 413"); + checkContains(response, offset, "HTTP/1.1 413"); } catch(Exception e) { @@ -424,14 +423,14 @@ public class HttpConnectionTest request.append("\015\012"); response = connector.getResponses(request.toString()); - offset = checkContains(response, offset, "HTTP/1.1 413"); + checkContains(response, offset, "HTTP/1.1 413"); } @Test public void testOversizedResponse() throws Exception { - String str = "thisisastringthatshouldreachover1kbytes"; - for (int i=0;i<400;i++) + String str = "thisisastringthatshouldreachover1kbytes-"; + for (int i=0;i<500;i++) str+="xxxxxxxxxxxx"; final String longstr = str; @@ -470,7 +469,7 @@ public class HttpConnectionTest "\015\012" ); - offset = checkContains(response, offset, "HTTP/1.1 500"); + checkContains(response, offset, "HTTP/1.1 500"); } catch(Exception e) { @@ -549,7 +548,7 @@ public class HttpConnectionTest response=connector.getResponses("CONNECT www.webtide.com:8080 HTTP/1.1\n"+ "Host: myproxy:8888\015\012"+ "\015\012"); - offset = checkContains(response,offset,"HTTP/1.1 200"); + checkContains(response,offset,"HTTP/1.1 200"); } catch (Exception e) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java index 8b1c982db7f..bb0d105a99f 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java @@ -13,10 +13,6 @@ package org.eclipse.jetty.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; - import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -29,18 +25,23 @@ import java.net.URL; import java.util.Arrays; import java.util.Random; import java.util.concurrent.Exchanger; - import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import junit.framework.Assert; - +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + /** * */ @@ -54,7 +55,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture private static final String REQUEST1=REQUEST1_HEADER+REQUEST1_CONTENT.getBytes().length+"\n\n"+REQUEST1_CONTENT; /** The expected response. */ - private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty("+Server.getVersion()+")\n"+"\n"+"Hello world\n"; + private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Content-Length: 13\n"+"Server: Jetty("+Server.getVersion()+")\n"+"\n"+"Hello world\n"; // Break the request up into three pieces, splitting the header. private static final String FRAGMENT1=REQUEST1.substring(0,16); @@ -62,12 +63,12 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture private static final String FRAGMENT3=REQUEST1.substring(34); /** Second test request. */ - private static final String REQUEST2_HEADER= + protected static final String REQUEST2_HEADER= "POST / HTTP/1.0\n"+ "Host: localhost\n"+ "Content-Type: text/xml;charset=ISO-8859-1\n"+ "Content-Length: "; - private static final String REQUEST2_CONTENT= + protected static final String REQUEST2_CONTENT= "\n"+ "\n"+ @@ -77,10 +78,10 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture " \n"+ " \n"+ ""; - private static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT; + protected static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT; /** The second expected response. */ - private static final String RESPONSE2_CONTENT= + protected static final String RESPONSE2_CONTENT= "\n"+ "\n"+ @@ -90,7 +91,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture " \n"+ " \n" +"\n"; - private static final String RESPONSE2= + protected static final String RESPONSE2= "HTTP/1.1 200 OK\n"+ "Content-Type: text/xml;charset=ISO-8859-1\n"+ "Content-Length: "+RESPONSE2_CONTENT.getBytes().length+"\n"+ @@ -106,7 +107,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * Feed the server the entire request at once. */ @Test - public void testRequest1_jetty() throws Exception + public void testRequest1() throws Exception { configureServer(new HelloWorldHandler()); @@ -140,19 +141,24 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture { OutputStream os=client.getOutputStream(); - os.write(("GET /R2 HTTP/1.1\015\012"+"Host: localhost\015\012"+"Transfer-Encoding: chunked\015\012"+"Content-Type: text/plain\015\012" - +"Connection: close\015\012"+"\015\012").getBytes()); + os.write(("GET /R2 HTTP/1.1\015\012"+ + "Host: localhost\015\012"+ + "Transfer-Encoding: chunked\015\012"+ + "Content-Type: text/plain\015\012"+ + "Connection: close\015\012"+ + "\015\012").getBytes()); os.flush(); Thread.sleep(PAUSE); os.write(("5\015\012").getBytes()); os.flush(); Thread.sleep(PAUSE); - os.write(("ABCDE\015\012"+"0;\015\012\015\012").getBytes()); + os.write(("ABCDE\015\012"+ + "0;\015\012\015\012").getBytes()); os.flush(); // Read the response. String response=readResponse(client); - assertTrue(true); // nothing checked yet. + assertTrue (response.indexOf("200")>0); } finally { @@ -164,7 +170,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * Feed the server fragmentary headers and see how it copes with it. */ @Test - public void testRequest1Fragments_jetty() throws Exception, InterruptedException + public void testRequest1Fragments() throws Exception, InterruptedException { configureServer(new HelloWorldHandler()); @@ -197,7 +203,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2_jetty() throws Exception + public void testRequest2() throws Exception { configureServer(new EchoHandler()); @@ -214,10 +220,16 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture // Read the response String response=readResponse(client); - + // Check the response assertEquals("response "+i,RESPONSE2,response); } + catch(IOException e) + { + e.printStackTrace(); + _server.dumpStdErr(); + throw e; + } finally { client.close(); @@ -226,7 +238,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2Fragments_jetty() throws Exception + public void testRequest2Fragments() throws Exception { configureServer(new EchoHandler()); @@ -270,7 +282,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2Iterate_jetty() throws Exception + public void testRequest2Iterate() throws Exception { configureServer(new EchoHandler()); @@ -309,7 +321,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * After several iterations, I generated some known bad fragment points. */ @Test - public void testRequest2KnownBad_jetty() throws Exception + public void testRequest2KnownBad() throws Exception { configureServer(new EchoHandler()); @@ -364,7 +376,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture try { URL url=new URL(_scheme+"://"+HOST+":"+_connector.getLocalPort()+"/?writes="+w+"&block="+b+ (e==0?"":("&encoding="+encoding[e]))+(c==0?"&chars=true":"")); - + InputStream in = (InputStream)url.getContent(); String response=IO.toString(in,e==0?null:encoding[e]); @@ -425,7 +437,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture while(len>=0) { - Thread.sleep(500); + Thread.sleep(100); len=is.read(buf); if (len>0) total+=len; @@ -439,7 +451,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture client.close(); } } - + @Test public void testBlockingWhileWritingResponseContent() throws Exception { @@ -447,6 +459,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture long start=System.currentTimeMillis(); Socket client=newSocket(HOST,_connector.getLocalPort()); + int total=0; try { OutputStream os=client.getOutputStream(); @@ -461,7 +474,6 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture ).getBytes()); os.flush(); - int total=0; int len=0; byte[] buf=new byte[1024*32]; @@ -480,6 +492,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } finally { + System.err.println("Got "+total+" of "+(512*1024)); client.close(); } } @@ -490,23 +503,23 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture configureServer(new BigBlockHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); - client.setSoTimeout(10000); + client.setSoTimeout(20000); try { OutputStream os=client.getOutputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); os.write(( - "GET / HTTP/1.1\r\n"+ + "GET /r1 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "\r\n"+ - "GET / HTTP/1.1\r\n"+ + "GET /r2 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "\r\n" ).getBytes()); os.flush(); - + // read the chunked response header boolean chunked=false; boolean closed=false; @@ -515,13 +528,13 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture String line=in.readLine(); if (line==null || line.length()==0) break; - + chunked|="Transfer-Encoding: chunked".equals(line); closed|="Connection: close".equals(line); } Assert.assertTrue(chunked); Assert.assertFalse(closed); - + // Read the chunks int max=Integer.MIN_VALUE; while(true) @@ -535,22 +548,22 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture if (max0); index=in.indexOf("123456789",index+1); assertTrue(index>0); index=in.indexOf("123456789",index+1); assertTrue(index==-1); - + } finally { client.close(); } } - + @Test public void testRecycledReaders() throws Exception { @@ -889,31 +907,93 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture "Connection: Keep-Alive\r\n"+ "\r\n" ).getBytes()); - + // Never send a body. // HelloWorldHandler does not read content, so 100 is not sent. // So close will have to happen anyway, without reset! - + os.flush(); - + client.setSoTimeout(2000); long start=System.currentTimeMillis(); String in = IO.toString(is); assertTrue(System.currentTimeMillis()-start<1000); assertTrue(in.indexOf("Connection: close")>0); assertTrue(in.indexOf("Hello world")>0); - + } finally { client.close(); } } - + + @Test + public void testCommittedError() throws Exception + { + CommittedErrorHandler handler =new CommittedErrorHandler(); + configureServer(handler); + + Socket client=newSocket(HOST,_connector.getLocalPort()); + try + { + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true); + OutputStream os=client.getOutputStream(); + InputStream is=client.getInputStream(); + + // Send a request + os.write(( + "GET / HTTP/1.1\r\n"+ + "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n" + + "\r\n" + ).getBytes()); + os.flush(); + + client.setSoTimeout(2000); + String in = IO.toString(is); + + assertEquals(-1,is.read()); // Closed by error! + + assertTrue(in.indexOf("HTTP/1.1 200 OK")>=0); + assertTrue(in.indexOf("Transfer-Encoding: chunked")>0); + assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party")>0); + assertTrue(in.indexOf("\r\n0\r\n")==-1); // chunking is interrupted by error close + + client.close(); + Thread.sleep(100); + assertTrue(!handler._endp.isOpen()); + } + finally + { + ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false); + + if (!client.isClosed()) + client.close(); + } + } + + protected static class CommittedErrorHandler extends AbstractHandler + { + public EndPoint _endp; + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + _endp=baseRequest.getConnection().getEndPoint(); + response.setHeader("test","value"); + response.setStatus(200); + response.setContentType("text/plain"); + response.getWriter().println("Now is the time for all good men to come to the aid of the party"); + response.getWriter().flush(); + response.flushBuffer(); + + throw new ServletException(new Exception("exception after commit")); + } + } + protected static class AvailableHandler extends AbstractHandler { public Exchanger _ex = new Exchanger(); - + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); @@ -921,31 +1001,31 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture response.setContentType("text/plain"); InputStream in = request.getInputStream(); ServletOutputStream out=response.getOutputStream(); - + // should always be some input available, because of deferred dispatch. int avail=in.available(); out.println(avail); - + String buf=""; for (int i=0;i0); @@ -1019,7 +1098,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture assertEquals(20,Integer.parseInt(reader.readLine())); assertEquals(0,Integer.parseInt(reader.readLine())); assertEquals("1234567890abcdefghijklmnopqrst",reader.readLine()); - + } finally { @@ -1027,7 +1106,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } } - + @Test public void testDualRequest1() throws Exception { @@ -1067,15 +1146,15 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * @return The response string. * @throws IOException in case of I/O problems */ - private static String readResponse(Socket client) throws IOException + protected static String readResponse(Socket client) throws IOException { BufferedReader br=null; + StringBuilder sb=new StringBuilder(); try { br=new BufferedReader(new InputStreamReader(client.getInputStream())); - StringBuilder sb=new StringBuilder(); String line; while ((line=br.readLine())!=null) @@ -1086,6 +1165,11 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture return sb.toString(); } + catch(IOException e) + { + System.err.println(e+" while reading '"+sb+"'"); + throw e; + } finally { if (br!=null) @@ -1120,5 +1204,5 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } - + } 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 268491098ef..09e1970c3e9 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 @@ -26,7 +26,7 @@ import org.junit.AfterClass; public class HttpServerTestFixture { // Useful constants protected static final long PAUSE=10L; - protected static final int LOOPS=Stress.isEnabled()?250:25; + protected static final int LOOPS=Stress.isEnabled()?250:50; protected static final String HOST="localhost"; protected static Server _server; @@ -36,7 +36,7 @@ public class HttpServerTestFixture protected Socket newSocket(String host,int port) throws Exception { Socket socket = new Socket(host,port); - socket.setSoTimeout(30000); + socket.setSoTimeout(10000); socket.setTcpNoDelay(true); socket.setSoLinger(false,0); return socket; @@ -189,7 +189,7 @@ public class HttpServerTestFixture } // Create a trust manager that does not validate certificate chains - public static TrustManager[] __trustAllCerts = new TrustManager[] { + public final static TrustManager[] __trustAllCerts = new TrustManager[] { new X509TrustManager(){ public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; @@ -203,7 +203,7 @@ public class HttpServerTestFixture } }; - public static HostnameVerifier __hostnameverifier = new HostnameVerifier() + public final static HostnameVerifier __hostnameverifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java index 3ab42194a01..d75235d40e0 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java @@ -27,9 +27,7 @@ import junit.framework.Assert; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.util.MultiMap; -import org.eclipse.jetty.util.URIUtil; import org.junit.Test; -import org.omg.Dynamic.Parameter; public class HttpURITest { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index fa86c3dcba0..3d21c413757 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java @@ -12,6 +12,7 @@ import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.SimpleBuffers; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; @@ -51,7 +52,7 @@ public class HttpWriterTest } @Override - public long flushBuffer() throws IOException + public int flushBuffer() throws IOException { return 0; } @@ -75,7 +76,17 @@ public class HttpWriterTest }; - HttpOutput httpOut = new HttpOutput(generator,60000); + AbstractHttpConnection connection = new AbstractHttpConnection(null,endp,new Server(),null,generator,null) + { + @Override + public Connection handle() throws IOException + { + return null; + } + }; + endp.setMaxIdleTime(60000); + + HttpOutput httpOut = new HttpOutput(connection); _writer = new HttpWriter(httpOut); } @@ -158,7 +169,17 @@ public class HttpWriterTest hb.setResponse(200,"OK"); - HttpOutput output = new HttpOutput(hb,10000); + AbstractHttpConnection connection = new AbstractHttpConnection(null,endp,new Server(),null,hb,null) + { + @Override + public Connection handle() throws IOException + { + return null; + } + }; + endp.setMaxIdleTime(10000); + hb.setSendServerVersion(false); + HttpOutput output = new HttpOutput(connection); HttpWriter writer = new HttpWriter(output); writer.setCharacterEncoding(StringUtil.__UTF8); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java new file mode 100644 index 00000000000..3042c767e98 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java @@ -0,0 +1,161 @@ +// ======================================================================== +// Copyright (c) 2004-2009 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.assertTrue; + + + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.server.session.SessionHandler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class LocalAsyncContextTest +{ + protected Server _server = new Server(); + protected SuspendHandler _handler = new SuspendHandler(); + protected Connector _connector; + + @Before + public void init() throws Exception + { + _connector = initConnector(); + _server.setConnectors(new Connector[]{ _connector }); + + SessionHandler session = new SessionHandler(); + session.setHandler(_handler); + + _server.setHandler(session); + _server.start(); + } + + protected Connector initConnector() + { + return new LocalConnector(); + } + + @After + public void destroy() throws Exception + { + _server.stop(); + _server.join(); + } + + @Test + public void testSuspendResume() throws Exception + { + _handler.setRead(0); + _handler.setSuspendFor(1000); + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(-1); + check("TIMEOUT",process(null)); + + _handler.setSuspendFor(10000); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process(null)); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process(null)); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process(null)); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(200); + check("COMPLETED",process(null)); + + _handler.setRead(-1); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(100); + check("COMPLETED",process("wibble")); + + _handler.setRead(6); + + _handler.setResumeAfter(0); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(100); + _handler.setCompleteAfter(-1); + check("RESUMED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(0); + check("COMPLETED",process("wibble")); + + _handler.setResumeAfter(-1); + _handler.setCompleteAfter(100); + check("COMPLETED",process("wibble")); + } + + protected void check(String content,String response) + { + assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertTrue(response.contains(content)); + } + + private synchronized String process(String content) throws Exception + { + String request = "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n"+ + "Connection: close\r\n"; + + if (content==null) + request+="\r\n"; + else + request+="Content-Length: "+content.length()+"\r\n" +"\r\n" + content; + + return getResponse(request); + } + + protected String getResponse(String request) throws Exception + { + return ((LocalConnector)_connector).getResponses(request); + } + + + static ContinuationListener __asyncListener = new ContinuationListener() + { + public void onComplete(Continuation continuation) + { + } + + public void onTimeout(Continuation continuation) + { + continuation.setAttribute("TIMEOUT",Boolean.TRUE); + continuation.resume(); + } + }; +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java index 551fac58f05..56e8e66b68b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java @@ -13,6 +13,9 @@ package org.eclipse.jetty.server; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -21,6 +24,7 @@ import java.net.Socket; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; + import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -33,9 +37,6 @@ import org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector; import org.junit.After; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class NetworkTrafficListenerTest { private static final byte END_OF_CONTENT = '~'; @@ -94,10 +95,10 @@ public class NetworkTrafficListenerTest // Connect to the server Socket socket = new Socket("localhost", port); - assertTrue(openedLatch.await(1, TimeUnit.SECONDS)); - + assertTrue(openedLatch.await(10, TimeUnit.SECONDS)); + socket.close(); - assertTrue(closedLatch.await(1, TimeUnit.SECONDS)); + assertTrue(closedLatch.await(10, TimeUnit.SECONDS)); } @Test diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java index 5492ac5a437..d072621a772 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java @@ -29,8 +29,6 @@ import java.util.List; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.StdErrLog; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -723,7 +721,7 @@ public class RFC2616Test "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+ "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"); - + offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1; offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1; @@ -732,7 +730,6 @@ public class RFC2616Test offset=checkContains(response,offset,"/R1","19.6.2 Keep-alive 1")+1; offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11; - offset=checkContains(response,offset,"Connection: close","19.6.2 Keep-alive close")+1; offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3; assertEquals("19.6.2 closed",-1,response.indexOf("/R3")); @@ -756,7 +753,6 @@ public class RFC2616Test offset=checkContains(response,offset,"ABCDEFGHIJ","19.6.2 Keep-alive 1")+1; offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11; - offset=checkContains(response,offset,"Connection: close","19.6.2 Keep-alive close")+1; offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3; assertEquals("19.6.2 closed",-1,response.indexOf("/R3")); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index f9eafc8df57..bc4cbff2cdc 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -19,20 +19,30 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import junit.framework.Assert; + +import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -68,7 +78,75 @@ public class RequestTest _server.stop(); _server.join(); } + + @Test + public void testParamExtraction() throws Exception + { + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + Map map = null; + try + { + //do the parse + request.getParameterMap(); + Assert.fail("Expected parsing failure"); + return false; + } + catch (Exception e) + { + //catch the error and check the param map is not null + map = request.getParameterMap(); + System.err.println(map); + assertFalse(map == null); + assertTrue(map.isEmpty()); + + Enumeration names = request.getParameterNames(); + assertFalse(names.hasMoreElements()); + } + + return true; + } + }; + + //Send a request with query string with illegal hex code to cause + //an exception parsing the params + String request="GET /?param=%ZZaaa HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: text/html;charset=utf8\n"+ + "\n"; + + String responses=_connector.getResponses(request); + assertTrue(responses.startsWith("HTTP/1.1 200")); + + } + + @Test + public void testBadUtf8ParamExtraction() throws Exception + { + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + String value=request.getParameter("param"); + return value.startsWith("aaa") && value.endsWith("bb"); + } + }; + + //Send a request with query string with illegal hex code to cause + //an exception parsing the params + String request="GET /?param=aaa%E7bbb HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: text/html;charset=utf8\n"+ + "\n"; + + String responses=_connector.getResponses(request); + assertTrue(responses.startsWith("HTTP/1.1 200")); + } + + @Test public void testContentTypeEncoding() throws Exception { @@ -420,13 +498,12 @@ public class RequestTest "\n" ); assertTrue(response.indexOf("200")>0); - assertTrue(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.0\n"+ "Host: whatever\n"+ - "Connection: Other, keep-alive\n"+ + "Connection: Other,,keep-alive\n"+ "\n" ); assertTrue(response.indexOf("200")>0); @@ -669,6 +746,56 @@ public class RequestTest assertEquals(null,cookie[1]); } + + @Test + public void testHashDOS() throws Exception + { + _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1); + _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000); + + // This file is not distributed - as it is dangerous + File evil_keys = new File("/tmp/keys_mapping_to_zero_2m"); + if (!evil_keys.exists()) + { + Log.info("testHashDOS skipped"); + return; + } + + BufferedReader in = new BufferedReader(new FileReader(evil_keys)); + StringBuilder buf = new StringBuilder(4000000); + + String key=null; + buf.append("a=b"); + while((key=in.readLine())!=null) + { + buf.append("&").append(key).append("=").append("x"); + } + buf.append("&c=d"); + + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + return "b".equals(request.getParameter("a")) && request.getParameter("c")==null; + } + }; + + String request="POST / HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+ + "Content-Length: "+buf.length()+"\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + buf; + + long start=System.currentTimeMillis(); + String response = _connector.getResponses(request); + assertTrue(response.contains("200 OK")); + long now=System.currentTimeMillis(); + assertTrue((now-start)<5000); + } + + interface RequestTester { boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException; @@ -683,13 +810,15 @@ public class RequestTest { ((Request)request).setHandled(true); - if (request.getContentLength()>0) + if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType())) _content=IO.toString(request.getInputStream()); - + if (_checker!=null && _checker.check(request,response)) response.setStatus(200); else response.sendError(500); + + } } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 29ab7d91e03..6f2b1172f44 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -29,6 +29,7 @@ import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSessionContext; @@ -80,7 +81,7 @@ public class ResponseTest @Test public void testContentType() throws Exception { - HttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); + AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); Response response = connection.getResponse(); assertEquals(null,response.getContentType()); @@ -134,7 +135,7 @@ public class ResponseTest public void testLocale() throws Exception { - HttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); + AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); Request request = connection.getRequest(); Response response = connection.getResponse(); ContextHandler context = new ContextHandler(); @@ -158,9 +159,8 @@ public class ResponseTest @Test public void testContentTypeCharacterEncoding() throws Exception { - HttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); + AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); - Request request = connection.getRequest(); Response response = connection.getResponse(); @@ -332,7 +332,7 @@ public class ResponseTest public void testEncodeRedirect() throws Exception { - HttpConnection connection=new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); + AbstractHttpConnection connection=new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()); Response response = new Response(connection); Request request = connection.getRequest(); request.setServerName("myhost"); @@ -381,31 +381,45 @@ public class ResponseTest public void testSendRedirect() throws Exception { - ByteArrayEndPoint out=new ByteArrayEndPoint(new byte[]{},4096); - HttpConnection connection=new TestHttpConnection(connector,out, connector.getServer()); - Response response = new Response(connection); - Request request = connection.getRequest(); - request.setServerName("myhost"); - request.setServerPort(8888); - request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target")); - request.setContextPath("/path"); - request.setRequestedSessionId("12345"); - request.setRequestedSessionIdFromCookie(false); - AbstractSessionManager manager=new HashSessionManager(); - manager.setSessionIdManager(new HashSessionIdManager()); - request.setSessionManager(manager); - request.setSession(new TestSession(manager,"12345")); - manager.setCheckingRemoteSessionIdEncoding(false); + String[][] tests={ + {"/other/location?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"}, + {"/other/location","http://myhost:8888/other/location"}, + {"/other/l%20cation","http://myhost:8888/other/l%20cation"}, + {"location","http://myhost:8888/path/location"}, + {"./location","http://myhost:8888/path/location"}, + {"../location","http://myhost:8888/location"}, + {"/other/l%20cation","http://myhost:8888/other/l%20cation"}, + {"l%20cation","http://myhost:8888/path/l%20cation"}, + {"./l%20cation","http://myhost:8888/path/l%20cation"}, + {"../l%20cation","http://myhost:8888/l%20cation"}, + }; + + for (int i=1;i0); + AbstractSessionManager manager=new HashSessionManager(); + manager.setSessionIdManager(new HashSessionIdManager()); + request.setSessionManager(manager); + request.setSession(new TestSession(manager,"12345")); + manager.setCheckingRemoteSessionIdEncoding(false); - response.sendRedirect("/other/location"); - - String location = out.getOut().toString(); - int l=location.indexOf("Location: "); - int e=location.indexOf('\n',l); - location=location.substring(l+10,e).trim(); - - assertEquals("http://myhost:8888/other/location;jsessionid=12345",location); - + response.sendRedirect(tests[i][0]); + + String location = out.getOut().toString(); + int l=location.indexOf("Location: "); + int e=location.indexOf('\n',l); + location=location.substring(l+10,e).trim(); + assertEquals(tests[i][0],tests[i][1],location); + } } @Test @@ -472,14 +486,32 @@ public class ResponseTest } } + @Test + public void testAddCookie() throws Exception + { + Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer())); + + Cookie cookie=new Cookie("name","value"); + cookie.setDomain("domain"); + cookie.setPath("/path"); + cookie.setSecure(true); + cookie.setComment("comment__HTTP_ONLY__"); + + response.addCookie(cookie); + + String set = response.getHttpFields().getStringField("Set-Cookie"); + + assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly",set); + } + private Response newResponse() { ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); endPoint.setOut(new ByteArrayBuffer(1024)); endPoint.setGrowOutput(true); - HttpConnection connection=new TestHttpConnection(connector, endPoint, connector.getServer()); - connection.getGenerator().reset(false); - HttpConnection.setCurrentConnection(connection); + AbstractHttpConnection connection=new TestHttpConnection(connector, endPoint, connector.getServer()); + connection.getGenerator().reset(); + AbstractHttpConnection.setCurrentConnection(connection); Response response = connection.getResponse(); connection.getRequest().setRequestURI("/test"); return response; @@ -579,7 +611,7 @@ public class ResponseTest } } - static class TestHttpConnection extends HttpConnection + static class TestHttpConnection extends AbstractHttpConnection { public TestHttpConnection(Connector connector, EndPoint endpoint, Server server) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java new file mode 100644 index 00000000000..3bd3cf3920e --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java @@ -0,0 +1,80 @@ +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.IO; +import org.junit.Test; + +public class SelectChannelAsyncContextTest extends LocalAsyncContextTest +{ + volatile SelectChannelEndPoint _endp; + + @Override + protected Connector initConnector() + { + return new SelectChannelConnector(){ + + @Override + public void customize(EndPoint endpoint, Request request) throws IOException + { + super.customize(endpoint,request); + _endp=(SelectChannelEndPoint)endpoint; + } + + }; + } + + @Override + protected String getResponse(String request) throws Exception + { + SelectChannelConnector connector = (SelectChannelConnector)_connector; + Socket socket = new Socket((String)null,connector.getLocalPort()); + socket.getOutputStream().write(request.getBytes("UTF-8")); + return IO.toString(socket.getInputStream()); + } + + @Test + public void testSuspendResumeWithAsyncDispatch() throws Exception + { + // Test that suspend/resume works in the face of spurious asyncDispatch call that may be + // produced by the SslConnection + final AtomicBoolean running = new AtomicBoolean(true); + Thread thread = new Thread() + { + public void run() + { + while (running.get()) + { + try + { + TimeUnit.MILLISECONDS.sleep(200); + SelectChannelEndPoint endp=_endp; + if (endp!=null && endp.isOpen()) + endp.asyncDispatch(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + }; + + try + { + thread.start(); + testSuspendResume(); + } + finally + { + running.set(false); + thread.join(); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java index ba0569f231b..d32593a83e2 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java @@ -25,10 +25,12 @@ public class SelectChannelServerTest extends HttpServerTestBase { startServer(new SelectChannelConnector()); } - + @Override - public void testBigBlocks() throws Exception + public void testCommittedError() throws Exception { - super.testBigBlocks(); + super.testCommittedError(); } + + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java index ece6ae77b9f..c3479cb4d4a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java @@ -13,17 +13,96 @@ package org.eclipse.jetty.server; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.Socket; + import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.junit.BeforeClass; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.util.IO; +import org.junit.Before; +import org.junit.Test; public class SelectChannelTimeoutTest extends ConnectorTimeoutTest { - @BeforeClass - public static void init() throws Exception + + @Before + public void init() throws Exception { SelectChannelConnector connector = new SelectChannelConnector(); - connector.setMaxIdleTime(MAX_IDLE_TIME); //250 msec max idle + connector.setMaxIdleTime(MAX_IDLE_TIME); // 250 msec max idle startServer(connector); } + @Test + public void testIdleTimeoutAfterSuspend() throws Exception + { + SuspendHandler _handler = new SuspendHandler(); + _server.stop(); + SessionHandler session = new SessionHandler(); + session.setHandler(_handler); + _server.setHandler(session); + _server.start(); + + _handler.setSuspendFor(100); + _handler.setResumeAfter(25); + assertTrue(process(null).toUpperCase().contains("RESUMED")); + } + + @Test + public void testIdleTimeoutAfterTimeout() throws Exception + { + SuspendHandler _handler = new SuspendHandler(); + _server.stop(); + SessionHandler session = new SessionHandler(); + session.setHandler(_handler); + _server.setHandler(session); + _server.start(); + + _handler.setSuspendFor(50); + assertTrue(process(null).toUpperCase().contains("TIMEOUT")); + } + + @Test + public void testIdleTimeoutAfterComplete() throws Exception + { + SuspendHandler _handler = new SuspendHandler(); + _server.stop(); + SessionHandler session = new SessionHandler(); + session.setHandler(_handler); + _server.setHandler(session); + _server.start(); + + _handler.setSuspendFor(100); + _handler.setCompleteAfter(25); + assertTrue(process(null).toUpperCase().contains("COMPLETED")); + } + + private synchronized String process(String content) throws UnsupportedEncodingException, IOException, InterruptedException + { + String request = "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n"; + + if (content == null) + request += "\r\n"; + else + request += "Content-Length: " + content.length() + "\r\n" + "\r\n" + content; + return getResponse(request); + } + + private String getResponse(String request) throws UnsupportedEncodingException, IOException, InterruptedException + { + SelectChannelConnector connector = (SelectChannelConnector)_connector; + Socket socket = new Socket((String)null,connector.getLocalPort()); + socket.getOutputStream().write(request.getBytes("UTF-8")); + InputStream inputStream = socket.getInputStream(); + String response = IO.toString(inputStream); + Thread.sleep(500); + assertEquals("Socket should be closed and return -1 on reading",-1,socket.getInputStream().read()); + return response; + } + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java index acf33226c9a..fafc061a56b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java @@ -24,14 +24,5 @@ public class SocketServerTest extends HttpServerTestBase public static void init() throws Exception { startServer(new SocketConnector()); - } - - @Override - public void testBlockingWhileReadingRequestContent() throws Exception - { - // TODO Auto-generated method stub - super.testBlockingWhileReadingRequestContent(); - } - - + } } 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 ccf1dcf1ac5..a7c640eb1ca 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 @@ -91,7 +91,7 @@ public class StressTest _server.setThreadPool(_threads); _connector = new SelectChannelConnector(); - _connector.setAcceptors(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + _connector.setAcceptors(1); _connector.setAcceptQueueSize(5000); _connector.setMaxIdleTime(30000); _server.addConnector(_connector); @@ -123,7 +123,7 @@ public class StressTest // TODO needs to be further investigated assumeTrue(!OS.IS_OSX || Stress.isEnabled()); - doThreads(10,100,false); + doThreads(10,10,false); if (Stress.isEnabled()) { Thread.sleep(1000); @@ -139,7 +139,7 @@ public class StressTest // TODO needs to be further investigated assumeTrue(!OS.IS_OSX || Stress.isEnabled()); - doThreads(20,100,true); + doThreads(20,10,true); if (Stress.isEnabled()) { Thread.sleep(1000); @@ -365,7 +365,6 @@ public class StressTest if (__tests.length!=bodies) System.err.println("responses=\n"+response+"\n---"); assertEquals(name,__tests.length,bodies); - bodies = count(response,"HTTP/1.1 200 OK"); long bind=connected-start; long flush=(written-connected)/__tests.length; @@ -461,7 +460,6 @@ public class StressTest response.setStatus(200); response.getOutputStream().print("DATA "+request.getPathInfo()+"\n\n"); baseRequest.setHandled(true); - long end=System.currentTimeMillis(); _latencies[4].add(new Long(System.currentTimeMillis()-start)); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java new file mode 100644 index 00000000000..f5ea64c3bd1 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java @@ -0,0 +1,153 @@ +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.HandlerWrapper; + +class SuspendHandler extends HandlerWrapper +{ + private int _read; + private long _suspendFor=-1; + private long _resumeAfter=-1; + private long _completeAfter=-1; + + public SuspendHandler() + { + } + + public int getRead() + { + return _read; + } + + public void setRead(int read) + { + _read = read; + } + + public long getSuspendFor() + { + return _suspendFor; + } + + public void setSuspendFor(long suspendFor) + { + _suspendFor = suspendFor; + } + + public long getResumeAfter() + { + return _resumeAfter; + } + + public void setResumeAfter(long resumeAfter) + { + _resumeAfter = resumeAfter; + } + + public long getCompleteAfter() + { + return _completeAfter; + } + + public void setCompleteAfter(long completeAfter) + { + _completeAfter = completeAfter; + } + + @Override + public void handle(String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException + { + if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) + { + if (_read>0) + { + byte[] buf=new byte[_read]; + request.getInputStream().read(buf); + } + else if (_read<0) + { + InputStream in = request.getInputStream(); + int b=in.read(); + while(b!=-1) + b=in.read(); + } + + final AsyncContext asyncContext = baseRequest.startAsync(); + asyncContext.addContinuationListener(LocalAsyncContextTest.__asyncListener); + if (_suspendFor>0) + asyncContext.setTimeout(_suspendFor); + + if (_completeAfter>0) + { + new Thread() { + @Override + public void run() + { + try + { + Thread.sleep(_completeAfter); + response.getOutputStream().print("COMPLETED"); + response.setStatus(200); + baseRequest.setHandled(true); + asyncContext.complete(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } + else if (_completeAfter==0) + { + response.getOutputStream().print("COMPLETED"); + response.setStatus(200); + baseRequest.setHandled(true); + asyncContext.complete(); + } + + if (_resumeAfter>0) + { + new Thread() { + @Override + public void run() + { + try + { + Thread.sleep(_resumeAfter); + if(((HttpServletRequest)asyncContext.getRequest()).getSession(true).getId()!=null) + asyncContext.dispatch(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } + else if (_resumeAfter==0) + { + asyncContext.dispatch(); + } + } + else if (request.getAttribute("TIMEOUT")!=null) + { + response.setStatus(200); + response.getOutputStream().print("TIMEOUT"); + baseRequest.setHandled(true); + } + else + { + response.setStatus(200); + response.getOutputStream().print("RESUMED"); + baseRequest.setHandled(true); + } + } + +} \ No newline at end of file diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java index 18ad49b3521..345e0ac13db 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java @@ -1,12 +1,10 @@ package org.eclipse.jetty.server.handler; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; import java.net.Socket; +import java.net.SocketTimeoutException; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; @@ -18,6 +16,9 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @version $Revision$ $Date$ */ @@ -91,11 +92,21 @@ public abstract class AbstractConnectHandlerTest StringBuilder body = new StringBuilder(); if (headers.containsKey("content-length")) { + int readLen = 0; int length = Integer.parseInt(headers.get("content-length")); - for (int i = 0; i < length; ++i) + try { - char c = (char)reader.read(); - body.append(c); + for (int i = 0; i < length; ++i) + { + char c = (char)reader.read(); + body.append(c); + readLen++; + } + } + catch (SocketTimeoutException e) + { + System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n",readLen,length); + throw e; } } else if ("chunked".equals(headers.get("transfer-encoding"))) @@ -126,7 +137,7 @@ public abstract class AbstractConnectHandlerTest protected Socket newSocket() throws IOException { Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); - socket.setSoTimeout(5000); + socket.setSoTimeout(10000); return socket; } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java index fe4b4ff337e..07977ee2472 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java @@ -24,10 +24,10 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.BeforeClass; import org.junit.Test; @@ -40,10 +40,11 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest public static void init() throws Exception { SslSelectChannelConnector connector = new SslSelectChannelConnector(); + connector.setMaxIdleTime(3600000); // TODO remove String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keyStorePath); + cf.setKeyStorePath(keyStorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); @@ -60,6 +61,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest "Host: " + hostPort + "\r\n" + "\r\n"; Socket socket = newSocket(); + socket.setSoTimeout(3600000); // TODO remove try { OutputStream output = socket.getOutputStream(); @@ -70,6 +72,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest // Expect 200 OK from the CONNECT request Response response = readResponse(input); + System.err.println(response); assertEquals("200", response.getCode()); // Be sure the buffered input does not have anything buffered @@ -82,7 +85,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest output = sslSocket.getOutputStream(); input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); - request = "" + + request = "GET /echo HTTP/1.1\r\n" + "Host: " + hostPort + "\r\n" + "\r\n"; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java index e61ab3eca39..78e04648d21 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java @@ -13,9 +13,9 @@ package org.eclipse.jetty.server.handler; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import java.io.IOException; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java index efb44bba5fe..0ca3fba82fc 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java @@ -14,23 +14,17 @@ package org.eclipse.jetty.server.handler; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import junit.framework.Assert; - import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Request; @@ -38,6 +32,10 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.resource.Resource; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + /** * @version $Revision$ */ @@ -122,22 +120,22 @@ public class ContextHandlerTest server.setConnectors(new Connector[] { connector }); ContextHandlerCollection contexts = new ContextHandlerCollection(); server.setHandler(contexts); - + ContextHandler rootA = new ContextHandler(contexts,"/"); ContextHandler fooA = new ContextHandler(contexts,"/foo"); ContextHandler foobarA = new ContextHandler(contexts,"/foo/bar"); - + server.start(); - + // System.err.println(server.dump()); - + Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/")); Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo")); Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar")); Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar/bob.jsp")); Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/other")); Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo/other")); - + Assert.assertEquals(rootA._scontext,foobarA._scontext.getContext("/")); Assert.assertEquals(fooA._scontext,foobarA._scontext.getContext("/foo")); Assert.assertEquals(foobarA._scontext,foobarA._scontext.getContext("/foo/bar")); @@ -145,7 +143,7 @@ public class ContextHandlerTest Assert.assertEquals(rootA._scontext,foobarA._scontext.getContext("/other")); Assert.assertEquals(fooA._scontext,foobarA._scontext.getContext("/foo/other")); } - + @Test public void testContextVirtualGetContext() throws Exception { @@ -154,45 +152,45 @@ public class ContextHandlerTest server.setConnectors(new Connector[] { connector }); ContextHandlerCollection contexts = new ContextHandlerCollection(); server.setHandler(contexts); - + ContextHandler rootA = new ContextHandler(contexts,"/"); rootA.setVirtualHosts(new String[] {"a.com"}); - + ContextHandler rootB = new ContextHandler(contexts,"/"); rootB.setVirtualHosts(new String[] {"b.com"}); - + ContextHandler rootC = new ContextHandler(contexts,"/"); rootC.setVirtualHosts(new String[] {"c.com"}); - - + + ContextHandler fooA = new ContextHandler(contexts,"/foo"); fooA.setVirtualHosts(new String[] {"a.com"}); - + ContextHandler fooB = new ContextHandler(contexts,"/foo"); fooB.setVirtualHosts(new String[] {"b.com"}); ContextHandler foobarA = new ContextHandler(contexts,"/foo/bar"); foobarA.setVirtualHosts(new String[] {"a.com"}); - + server.start(); - + // System.err.println(server.dump()); - + Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/")); Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo")); Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar")); Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar/bob")); - + Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/other")); Assert.assertEquals(rootB._scontext,rootB._scontext.getContext("/other")); Assert.assertEquals(rootC._scontext,rootC._scontext.getContext("/other")); - + Assert.assertEquals(fooB._scontext,rootB._scontext.getContext("/foo/other")); Assert.assertEquals(rootC._scontext,rootC._scontext.getContext("/foo/other")); } - - + + @Test public void testVirtualHostWildcard() throws Exception { @@ -232,6 +230,37 @@ public class ContextHandlerTest } } + @Test + public void testVirtualHostManagement() throws Exception + { + ContextHandler context = new ContextHandler("/"); + + // test singular + context.setVirtualHosts(new String[] { "www.example.com"} ); + Assert.assertEquals(1,context.getVirtualHosts().length); + + // test adding two more + context.addVirtualHosts(new String[] { "www.example2.com", "www.example3.com"}); + Assert.assertEquals(3,context.getVirtualHosts().length); + + // test adding existing context + context.addVirtualHosts(new String[] { "www.example.com" }); + Assert.assertEquals(3,context.getVirtualHosts().length); + + // test removing existing + context.removeVirtualHosts(new String[] { "www.example3.com" }); + Assert.assertEquals(2,context.getVirtualHosts().length); + + // test removing non-existent + context.removeVirtualHosts(new String[] { "www.example3.com" }); + Assert.assertEquals(2,context.getVirtualHosts().length); + + // test removing all remaining and resets to null + context.removeVirtualHosts(new String[] { "www.example.com", "www.example2.com" }); + Assert.assertEquals(null,context.getVirtualHosts()); + + } + @Test public void testAttributes() throws Exception { @@ -301,11 +330,12 @@ public class ContextHandlerTest return root; } - + @Test public void testUncheckedPrintWriter() throws Exception { Server server = new Server(); + server.setUncheckedPrintWriter(true); LocalConnector connector = new LocalConnector(); server.setConnectors(new Connector[] { connector }); ContextHandler context = new ContextHandler("/"); @@ -316,7 +346,7 @@ public class ContextHandlerTest try { server.start(); - + String response = connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n"); Assert.assertTrue(response.indexOf("Goodbye")>0); @@ -369,7 +399,7 @@ public class ContextHandlerTest handled = false; } } - + private static final class WriterHandler extends AbstractHandler { boolean error; @@ -391,7 +421,10 @@ public class ContextHandlerTest writer.write("Goodbye cruel world\n"); writer.close(); response.flushBuffer(); - writer.write("speaking from the dead"); + //writer.write("speaking from the dead"); + writer.write("give the printwriter a chance"); //should create an error + if (writer.checkError()) + writer.write("didn't take the chance, will throw now"); //write after an error } catch(Throwable th) { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java new file mode 100644 index 00000000000..866e1978a79 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -0,0 +1,76 @@ +package org.eclipse.jetty.server.handler; +//======================================================================== +//Copyright (c) 1999-2009 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. +//======================================================================== + +import java.net.URI; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.SimpleRequest; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Resource Handler test + * + * TODO: increase the testing going on here + */ +public class ResourceHandlerTest extends TestCase +{ + private static Server _server; + private static Connector _connector; + private static ContextHandler _contextHandler; + private static ResourceHandler _resourceHandler; + + + @BeforeClass + public void setUp() throws Exception + { + _server = new Server(); + _connector = new SocketConnector(); + _server.setConnectors(new Connector[] { _connector }); + + _resourceHandler = new ResourceHandler(); + + _contextHandler = new ContextHandler("/resource"); + _contextHandler.setHandler(_resourceHandler); + _server.setHandler(_contextHandler); + _server.start(); + } + + /* ------------------------------------------------------------ */ + @AfterClass + public void tearDown() throws Exception + { + _server.stop(); + } + + @Test + public void testSimpleResourceHandler() throws Exception + { + _resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("simple").getAbsolutePath()); + + SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); + + Assert.assertEquals("simple text", sr.getString("/resource/simple.txt")); + + Assert.assertNotNull("missing jetty.css" , sr.getString("/resource/jetty-dir.css")); + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java new file mode 100644 index 00000000000..0d46cc17496 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java @@ -0,0 +1,114 @@ +package org.eclipse.jetty.server.handler; + +//======================================================================== +//Copyright (c) 2009-2009 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. +//======================================================================== + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ShutdownHandlerTest +{ + @Mock private HttpServletRequest request; + @Mock private HttpServletResponse response; + + private Server server = new Server(0); + private String shutdownToken = "asdlnsldgnklns"; + + // class under test + private ShutdownHandler shutdownHandler; + + @Before + public void startServer() throws Exception + { + MockitoAnnotations.initMocks(this); + server.start(); + shutdownHandler = new ShutdownHandler(server,shutdownToken); + } + + @Test + public void shutdownServerWithCorrectTokenAndIPTest() throws Exception + { + setDefaultExpectations(); + final CountDownLatch countDown = new CountDownLatch(1); + server.addLifeCycleListener(new AbstractLifeCycle.Listener () + { + + public void lifeCycleStarting(LifeCycle event) + { + } + + public void lifeCycleStarted(LifeCycle event) + { + } + + public void lifeCycleFailure(LifeCycle event, Throwable cause) + { + } + + public void lifeCycleStopping(LifeCycle event) + { + } + + public void lifeCycleStopped(LifeCycle event) + { + countDown.countDown(); + } + + }); + shutdownHandler.handle("/shutdown",null,request,response); + boolean stopped = countDown.await(1000, TimeUnit.MILLISECONDS); //wait up to 1 sec to stop + assertTrue("Server lifecycle stop listener called", stopped); + assertEquals("Server should be stopped","STOPPED",server.getState()); + } + + @Test + public void wrongTokenTest() throws Exception + { + setDefaultExpectations(); + when(request.getParameter("token")).thenReturn("anothertoken"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + @Test + public void shutdownRequestNotFromLocalhostTest() throws Exception + { + setDefaultExpectations(); + when(request.getRemoteAddr()).thenReturn("192.168.3.3"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + private void setDefaultExpectations() + { + when(request.getMethod()).thenReturn("POST"); + when(request.getParameter("token")).thenReturn(shutdownToken); + when(request.getRemoteAddr()).thenReturn("127.0.0.1"); + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java new file mode 100644 index 00000000000..db9d0d71171 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java @@ -0,0 +1,181 @@ +//======================================================================== +//Copyright 2004-2008 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +//http://www.apache.org/licenses/LICENSE-2.0 +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. +//======================================================================== + +// JettyTest.java -- +// +// Junit test that shows the Jetty SSL bug. +// + +package org.eclipse.jetty.server.ssl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +/** + * HttpServer Tester. + */ +public class SSLCloseTest extends TestCase +{ + private static AsyncEndPoint __endp; + private static class CredulousTM implements TrustManager, X509TrustManager + { + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + return; + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + return; + } + } + + private static final TrustManager[] s_dummyTrustManagers=new TrustManager[] { new CredulousTM() }; + + // ~ Methods + // ---------------------------------------------------------------- + + /** + * Feed the server the entire request at once. + * + * @throws Exception + */ + public void testClose() throws Exception + { + Server server=new Server(); + SslSelectChannelConnector connector=new SslSelectChannelConnector(); + + String keystore = System.getProperty("user.dir")+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator+"keystore"; + + connector.setPort(0); + connector.getSslContextFactory().setKeyStorePath(keystore); + connector.getSslContextFactory().setKeyStorePassword("storepwd"); + connector.getSslContextFactory().setKeyManagerPassword("keypwd"); + + server.setConnectors(new Connector[] + { connector }); + server.setHandler(new WriteHandler()); + + server.start(); + + + SSLContext ctx=SSLContext.getInstance("SSLv3"); + ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom()); + + int port=connector.getLocalPort(); + + // System.err.println("write:"+i); + Socket socket=ctx.getSocketFactory().createSocket("localhost",port); + OutputStream os=socket.getOutputStream(); + + os.write("GET /test HTTP/1.1\r\nHost:test\r\nConnection:close\r\n\r\n".getBytes()); + os.flush(); + + BufferedReader in =new BufferedReader(new InputStreamReader(socket.getInputStream())); + + String line; + while ((line=in.readLine())!=null) + { + System.err.println(line); + if (line.trim().length()==0) + break; + } + + Thread.sleep(2000); + System.err.println(__endp); + + while ((line=in.readLine())!=null) + System.err.println(line); + + } + + + private static class WriteHandler extends AbstractHandler + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + response.setStatus(200); + response.setHeader("test","value"); + __endp=(AsyncEndPoint)baseRequest.getConnection().getEndPoint(); + + OutputStream out=response.getOutputStream(); + + String data = "Now is the time for all good men to come to the aid of the party.\n"; + data+="How now brown cow.\n"; + data+="The quick brown fox jumped over the lazy dog.\n"; + // data=data+data+data+data+data+data+data+data+data+data+data+data+data; + // data=data+data+data+data+data+data+data+data+data+data+data+data+data; + data=data+data+data+data; + byte[] bytes=data.getBytes("UTF-8"); + + for (int i=0;i<2;i++) + { + System.err.println("Write "+i+" "+bytes.length); + out.write(bytes); + } + } + catch(RuntimeException e) + { + e.printStackTrace(); + throw e; + } + catch(IOException e) + { + e.printStackTrace(); + throw e; + } + catch(Error e) + { + e.printStackTrace(); + throw e; + } + catch(Throwable e) + { + e.printStackTrace(); + throw new ServletException(e); + } + } + + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java index 0373a3f3e4f..c0d39e2bf22 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java @@ -44,13 +44,13 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -112,7 +112,7 @@ public class SSLEngineTest connector.setPort(0); SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore); + cf.setKeyStorePath(keystore); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); connector.setRequestBufferSize(512); @@ -133,7 +133,7 @@ public class SSLEngineTest @Test public void testBigResponse() throws Exception { - SSLContext ctx=SSLContext.getInstance("SSLv3"); + SSLContext ctx=SSLContext.getInstance("TLS"); ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom()); int port=connector.getLocalPort(); @@ -367,4 +367,5 @@ public class SSLEngineTest response.flushBuffer(); } } + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java index 32344768497..79ef7d32f21 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java @@ -22,10 +22,10 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -45,7 +45,7 @@ public class SSLSelectChannelConnectorLoadTest String keystorePath = System.getProperty("basedir", ".") + "/src/test/resources/keystore"; SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystorePath); + cf.setKeyStorePath(keystorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); cf.setTrustStore(keystorePath); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java similarity index 62% rename from jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java rename to jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java index 1acfa7d9e29..a7424b99497 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java @@ -12,22 +12,27 @@ // ======================================================================== package org.eclipse.jetty.server.ssl; +import static org.junit.Assert.assertEquals; + import java.io.FileInputStream; +import java.io.OutputStream; import java.net.Socket; import java.security.KeyStore; +import java.util.Arrays; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.HttpServerTestBase; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.BeforeClass; +import org.junit.Test; /** * HttpServer Tester. */ -public class SslSelectChannelServerTest extends HttpServerTestBase +public class SelectChannelServerSslTest extends HttpServerTestBase { static SSLContext __sslContext; { @@ -47,7 +52,7 @@ public class SslSelectChannelServerTest extends HttpServerTestBase SslSelectChannelConnector connector = new SslSelectChannelConnector(); String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystorePath); + cf.setKeyStorePath(keystorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); cf.setTrustStore(keystorePath); @@ -60,14 +65,14 @@ public class SslSelectChannelServerTest extends HttpServerTestBase keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keystore); - __sslContext = SSLContext.getInstance("SSL"); + __sslContext = SSLContext.getInstance("TLS"); __sslContext.init(null, trustManagerFactory.getTrustManagers(), null); try { HttpsURLConnection.setDefaultHostnameVerifier(__hostnameverifier); - SSLContext sc = SSLContext.getInstance("SSL"); + SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, __trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } @@ -75,9 +80,59 @@ public class SslSelectChannelServerTest extends HttpServerTestBase { e.printStackTrace(); throw new RuntimeException(e); - } - + } + } + + public void testRequest2Fragments() throws Exception + { + super.testRequest2Fragments(); } - + @Test + public void testRequest2FixedFragments() throws Exception + { + configureServer(new EchoHandler()); + + byte[] bytes=REQUEST2.getBytes(); + int[] points=new int[]{74,325}; + + // Sort the list + Arrays.sort(points); + + Socket client=newSocket(HOST,_connector.getLocalPort()); + try + { + OutputStream os=client.getOutputStream(); + + + int last=0; + + // Write out the fragments + for (int j=0; j= 0) - break; - } - - handleCount.set(0); - - // Send TCP FIN without SSL close alert - socket.close(); - - // Sleep for a while to detect eventual spin looping - TimeUnit.SECONDS.sleep(1); - - Assert.assertTrue("handle() invocations", handleCount.get()<=1); - Assert.assertTrue("endpoint not closed", endPointClosed.get()); - } - - /** - * This test is currently failing because we are looping on SslSCEP.unwrap() - * to fill the buffer, so there is a case where we loop once, read some data - * loop again and read -1, but we can't close the connection yet as we have - * to notify the application (not sure that this is necessary... must assume - * the data is truncated, so it's not that safe to pass it to the application). - * This case needs to be revisited, and it also requires a review of the - * Connection:close case, especially on the client side. - * @throws Exception if the test fails - */ - @Ignore - @Test - public void testTruncationAttackBeforeReading() throws Exception - { - Socket socket = new Socket("localhost", connector.getLocalPort()); - SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true); - sslSocket.setUseClientMode(true); - final CountDownLatch handshakeLatch = new CountDownLatch(1); - sslSocket.addHandshakeCompletedListener(new HandshakeCompletedListener() - { - public void handshakeCompleted(HandshakeCompletedEvent handshakeCompletedEvent) - { - handshakeLatch.countDown(); - } - }); - sslSocket.startHandshake(); - - Assert.assertTrue(handshakeLatch.await(1, TimeUnit.SECONDS)); - - String request = "" + - "GET / HTTP/1.1\r\n" + - "Host: localhost:" + connector.getLocalPort() + "\r\n" + - "\r\n"; - sslSocket.getOutputStream().write(request.getBytes("UTF-8")); - - // Do not read the response, just close the underlying socket - - handleCount.set(0); - - // Send TCP FIN without SSL close alert - socket.close(); - - // Sleep for a while to detect eventual spin looping - TimeUnit.SECONDS.sleep(1); - - Assert.assertEquals("handle() invocations", 1, handleCount.get()); - Assert.assertTrue("endpoint not closed", endPointClosed.get()); - } - - @Test - public void testTruncationAttackAfterHandshake() throws Exception - { - Socket socket = new Socket("localhost", connector.getLocalPort()); - SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true); - sslSocket.setUseClientMode(true); - final CountDownLatch handshakeLatch = new CountDownLatch(1); - sslSocket.addHandshakeCompletedListener(new HandshakeCompletedListener() - { - public void handshakeCompleted(HandshakeCompletedEvent handshakeCompletedEvent) - { - handshakeLatch.countDown(); - } - }); - sslSocket.startHandshake(); - - Assert.assertTrue(handshakeLatch.await(1, TimeUnit.SECONDS)); - - handleCount.set(0); - - // Send TCP FIN without SSL close alert - socket.close(); - - // Sleep for a while to detect eventual spin looping - TimeUnit.SECONDS.sleep(1); - - Assert.assertEquals("handle() invocations", 1, handleCount.get()); - Assert.assertTrue("endpoint not closed", endPointClosed.get()); - } - - - @Test - public void testTruncationAttackBeforeHandshake() throws Exception - { - Socket socket = new Socket("localhost", connector.getLocalPort()); - SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true); - sslSocket.setUseClientMode(true); - - // Wait for the socket to be connected - TimeUnit.SECONDS.sleep(1); - - handleCount.set(0); - - // Send TCP FIN without SSL close alert - socket.close(); - - // Sleep for a while to detect eventual spin looping - TimeUnit.SECONDS.sleep(1); - - Assert.assertEquals("handle() invocations", 1, handleCount.get()); - Assert.assertTrue("endpoint not closed", endPointClosed.get()); - } - - private class EmptyHandler extends AbstractHandler - { - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - } - } -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java index aa98e636392..b06acc1ea60 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.server.ssl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.FileInputStream; import java.io.IOException; @@ -31,11 +32,11 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -58,7 +59,7 @@ public class SslUploadTest String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystorePath); + cf.setKeyStorePath(keystorePath); cf.setKeyStorePassword("storepwd"); cf.setKeyManagerPassword("keypwd"); cf.setTrustStore(keystorePath); @@ -129,6 +130,7 @@ public class SslUploadTest InputStream in = socket.getInputStream(); String response = IO.toString(in); + assertTrue (response.indexOf("200")>0); // System.err.println(response); long end = System.nanoTime(); diff --git a/jetty-server/src/test/resources/simple/simple.txt b/jetty-server/src/test/resources/simple/simple.txt new file mode 100644 index 00000000000..f2403aead5a --- /dev/null +++ b/jetty-server/src/test/resources/simple/simple.txt @@ -0,0 +1 @@ +simple text \ No newline at end of file diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml index 47549f85fff..70ab5daf8a3 100644 --- a/jetty-servlet/pom.xml +++ b/jetty-servlet/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-servlet 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 b6ebf48708f..1f742af5e6d 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 @@ -22,6 +22,7 @@ import java.net.URL; import java.util.Enumeration; import java.util.List; import java.util.Map; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -39,9 +40,9 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.WriterOutputStream; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Dispatcher; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.InclusiveByteRange; import org.eclipse.jetty.server.ResourceCache; @@ -280,7 +281,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (h.getServletInstance()==this) _defaultHolder=h; - if (LOG.isDebugEnabled()) LOG.debug("resource base = "+_resourceBase); + if (LOG.isDebugEnabled()) + LOG.debug("resource base = "+_resourceBase); } /** @@ -770,7 +772,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } else { - Connector connector = HttpConnection.getCurrentConnection().getConnector(); + Connector connector = AbstractHttpConnection.getCurrentConnection().getConnector(); direct=connector instanceof NIOConnector && ((NIOConnector)connector).getUseDirectBuffers() && !(connector instanceof SslConnector); content_length=content.getContentLength(); } @@ -786,7 +788,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // has a filter already written to the response? written = out instanceof HttpOutput ? ((HttpOutput)out).isWritten() - : HttpConnection.getCurrentConnection().getGenerator().isWritten(); + : AbstractHttpConnection.getCurrentConnection().getGenerator().isWritten(); } catch(IllegalStateException e) { @@ -809,7 +811,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (response instanceof Response) { writeOptionHeaders(((Response)response).getHttpFields()); - ((HttpConnection.Output)out).sendContent(content); + ((AbstractHttpConnection.Output)out).sendContent(content); } else { @@ -817,7 +819,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (buffer!=null) { writeHeaders(response,content,content_length); - ((HttpConnection.Output)out).sendContent(buffer); + ((AbstractHttpConnection.Output)out).sendContent(buffer); } else { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java index af9629ce146..ada696e330e 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java @@ -18,14 +18,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Dispatcher; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; @@ -63,7 +64,7 @@ public class ErrorPageErrorHandler extends ErrorHandler String method = request.getMethod(); if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD)) { - HttpConnection.getCurrentConnection().getRequest().setHandled(true); + AbstractHttpConnection.getCurrentConnection().getRequest().setHandled(true); return; } if (_errorPages!=null) 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 a4fd20c328e..937dba53f18 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 @@ -19,12 +19,12 @@ import java.util.Collection; import java.util.EnumSet; import java.util.List; -import org.eclipse.jetty.server.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterConfig; -import org.eclipse.jetty.servlet.api.FilterRegistration; import javax.servlet.ServletException; +import org.eclipse.jetty.server.DispatcherType; +import org.eclipse.jetty.servlet.api.FilterRegistration; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java index bef03e29e15..7bd6559d6d8 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java @@ -14,17 +14,14 @@ package org.eclipse.jetty.servlet; import java.io.IOException; -import java.util.Arrays; import java.util.EnumSet; -import org.eclipse.jetty.server.DispatcherType; - import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.DispatcherType; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.util.log.Log; public class FilterMapping implements Dumpable 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 e1be344bdcf..cbbbb02ec9b 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 @@ -21,11 +21,11 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.eclipse.jetty.servlet.api.Registration; import javax.servlet.ServletContext; import javax.servlet.UnavailableException; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.servlet.api.Registration; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AggregateLifeCycle; @@ -82,7 +82,8 @@ public class Holder extends AbstractLifeCycle implements Dumpable try { _class=Loader.loadClass(Holder.class, _className); - if(LOG.isDebugEnabled())LOG.debug("Holding {}",_class); + if(LOG.isDebugEnabled()) + LOG.debug("Holding {}",_class); } catch (Exception e) { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java index 9a378264186..b4c0ca6a442 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -26,9 +26,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; @@ -140,7 +140,8 @@ public class Invoker extends HttpServlet { // Found a named servlet (from a user's web.xml file) so // now we add a mapping for it - LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*"); + if (LOG.isDebugEnabled()) + LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*"); ServletMapping mapping = new ServletMapping(); mapping.setServletName(servlet); mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*"); @@ -174,7 +175,8 @@ public class Invoker extends HttpServlet else { // Make a holder - LOG.debug("Making new servlet="+servlet+" with path="+path+"/*"); + if (LOG.isDebugEnabled()) + LOG.debug("Making new servlet="+servlet+" with path="+path+"/*"); holder=_servletHandler.addServletWithMapping(servlet, path+"/*"); if (_parameters!=null) @@ -211,7 +213,7 @@ public class Invoker extends HttpServlet } } - if (_verbose) + if (_verbose && LOG.isDebugEnabled()) LOG.debug("Dynamic load '"+servlet+"' at "+path); } } @@ -219,7 +221,7 @@ public class Invoker extends HttpServlet if (holder!=null) { - final Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); holder.handle(baseRequest, new InvokedRequest(request,included,servlet,servlet_path,path_info), response); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 09b60680021..43ad5d6542d 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -142,8 +142,10 @@ public class ServletContextHandler extends ContextHandler protected void doStop() throws Exception { super.doStop(); - _decorators.clear(); - _wrapper.setHandler(null); + if (_decorators != null) + _decorators.clear(); + if (_wrapper != null) + _wrapper.setHandler(null); } /* ------------------------------------------------------------ */ 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 45df833bf51..1a5e485f216 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 @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; @@ -26,7 +25,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; -import org.eclipse.jetty.server.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; @@ -46,8 +44,9 @@ import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Dispatcher; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.DispatcherType; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServletRequestHttpWrapper; @@ -390,27 +389,24 @@ public class ServletHandler extends ScopedHandler } if (LOG.isDebugEnabled()) - LOG.debug("servlet {} -> {}",baseRequest.getContextPath()+"|"+baseRequest.getServletPath()+"|"+baseRequest.getPathInfo(),servlet_holder); + LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder); try { // Do the filter/handling thang - if (servlet_holder!=null) - { - old_scope=baseRequest.getUserIdentityScope(); - baseRequest.setUserIdentityScope(servlet_holder); + old_scope=baseRequest.getUserIdentityScope(); + baseRequest.setUserIdentityScope(servlet_holder); - // start manual inline of nextScope(target,baseRequest,request,response); - if (never()) - nextScope(target,baseRequest,request,response); - else if (_nextScope!=null) - _nextScope.doScope(target,baseRequest,request, response); - else if (_outerScope!=null) - _outerScope.doHandle(target,baseRequest,request, response); - else - doHandle(target,baseRequest,request, response); - // end manual inline (pathentic attempt to reduce stack depth) - } + // start manual inline of nextScope(target,baseRequest,request,response); + if (never()) + nextScope(target,baseRequest,request,response); + else if (_nextScope!=null) + _nextScope.doScope(target,baseRequest,request, response); + else if (_outerScope!=null) + _outerScope.doHandle(target,baseRequest,request, response); + else + doHandle(target,baseRequest,request, response); + // end manual inline (pathentic attempt to reduce stack depth) } finally { @@ -455,13 +451,16 @@ public class ServletHandler extends ScopedHandler } } - LOG.debug("chain=",chain); + LOG.debug("chain={}",chain); try { if (servlet_holder==null) { - notFound(request, response); + if (getHandler()==null) + notFound(request, response); + else + nextHandle(target,baseRequest,request,response); } else { @@ -474,7 +473,6 @@ public class ServletHandler extends ScopedHandler res = ((ServletResponseHttpWrapper)res).getResponse(); // Do the filter/handling thang - baseRequest.setHandled(true); if (chain!=null) chain.doFilter(req, res); else @@ -583,6 +581,10 @@ public class ServletHandler extends ScopedHandler else LOG.debug("Response already committed for handling ",e); } + finally + { + baseRequest.setHandled(true); + } } /* ------------------------------------------------------------ */ @@ -1228,7 +1230,8 @@ public class ServletHandler extends ScopedHandler HttpServletResponse response) throws IOException { - if(LOG.isDebugEnabled())LOG.debug("Not Found "+request.getRequestURI()); + if(LOG.isDebugEnabled()) + LOG.debug("Not Found "+request.getRequestURI()); response.sendError(HttpServletResponse.SC_NOT_FOUND); } @@ -1315,7 +1318,9 @@ public class ServletHandler extends ScopedHandler /* ------------------------------------------------------------ */ public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException - { + { + final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); + // pass to next filter if (_filterHolder!=null) { @@ -1326,7 +1331,6 @@ public class ServletHandler extends ScopedHandler filter.doFilter(request, response, _next); else { - final Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); final boolean suspendable=baseRequest.isAsyncSupported(); if (suspendable) { @@ -1347,15 +1351,20 @@ public class ServletHandler extends ScopedHandler } // Call servlet + + HttpServletRequest srequest = (HttpServletRequest)request; if (_servletHolder != null) { if (LOG.isDebugEnabled()) LOG.debug("call servlet " + _servletHolder); - final Request baseRequest=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); _servletHolder.handle(baseRequest,request, response); } - else // Not found - notFound((HttpServletRequest)request, (HttpServletResponse)response); + else if (getHandler()==null) + notFound(srequest, (HttpServletResponse)response); + else + nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), + baseRequest,srequest,(HttpServletResponse)response); + } public String toString() @@ -1389,13 +1398,15 @@ public class ServletHandler extends ScopedHandler public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { - if (LOG.isDebugEnabled()) LOG.debug("doFilter " + _filter); + if (LOG.isDebugEnabled()) + LOG.debug("doFilter " + _filter); // pass to next filter if (_filter < LazyList.size(_chain)) { FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++); - if (LOG.isDebugEnabled()) LOG.debug("call filter " + holder); + if (LOG.isDebugEnabled()) + LOG.debug("call filter " + holder); Filter filter= holder.getFilter(); if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported()) @@ -1419,13 +1430,21 @@ public class ServletHandler extends ScopedHandler } // Call servlet + HttpServletRequest srequest = (HttpServletRequest)request; if (_servletHolder != null) { - if (LOG.isDebugEnabled()) LOG.debug("call servlet " + _servletHolder); + if (LOG.isDebugEnabled()) + LOG.debug("call servlet " + _servletHolder); _servletHolder.handle(_baseRequest,request, response); } - else // Not found - notFound((HttpServletRequest)request, (HttpServletResponse)response); + else if (getHandler()==null) + notFound(srequest, (HttpServletResponse)response); + else + { + Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); + nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), + baseRequest,srequest,(HttpServletResponse)response); + } } /* ------------------------------------------------------------ */ 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 1bcbe0822fd..59299c608c4 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 @@ -25,21 +25,20 @@ import java.util.Map; import java.util.Set; import java.util.Stack; - import javax.servlet.Servlet; import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import org.eclipse.jetty.servlet.api.ServletRegistration; - import javax.servlet.ServletContext; +import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.SingleThreadModel; import javax.servlet.UnavailableException; + import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.RunAsToken; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.servlet.api.ServletRegistration; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java index 6dd03442b81..285de0f3035 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java @@ -14,8 +14,6 @@ package org.eclipse.jetty.servlet.listener; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java index 4a07b031192..4a4f2fad1b5 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java @@ -1,8 +1,11 @@ package org.eclipse.jetty.servlet; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -11,6 +14,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import junit.framework.AssertionFailedError; + import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.FS; @@ -25,8 +29,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.assertTrue; - public class DefaultServletTest { @Rule diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java index e2d8694939f..b17796f0d4d 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -13,14 +13,21 @@ package org.eclipse.jetty.servlet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.GenericServlet; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; @@ -28,33 +35,48 @@ import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import junit.framework.Assert; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class DispatcherTest { private Server _server; private LocalConnector _connector; - private ServletContextHandler _context; - + private ContextHandlerCollection _contextCollection; + private ServletContextHandler _contextHandler; + private ResourceHandler _resourceHandler; + @Before public void init() throws Exception { _server = new Server(); _server.setSendServerVersion(false); _connector = new LocalConnector(); - _context = new ServletContextHandler(); - _context.setContextPath("/context"); - _server.setHandler(_context); + + _contextCollection = new ContextHandlerCollection(); + _contextHandler = new ServletContextHandler(); + _contextHandler.setContextPath("/context"); + _contextCollection.addHandler(_contextHandler); + _resourceHandler = new ResourceHandler(); + _resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("dispatchResourceTest").getAbsolutePath()); + ContextHandler resourceContextHandler = new ContextHandler("/resource"); + resourceContextHandler.setHandler(_resourceHandler); + _contextCollection.addHandler(resourceContextHandler); + _server.setHandler(_contextCollection); _server.addConnector( _connector ); _server.start(); @@ -70,8 +92,8 @@ public class DispatcherTest @Test public void testForward() throws Exception { - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(AssertForwardServlet.class, "/AssertForwardServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(AssertForwardServlet.class, "/AssertForwardServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -87,8 +109,8 @@ public class DispatcherTest @Test public void testInclude() throws Exception { - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(AssertIncludeServlet.class, "/AssertIncludeServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(AssertIncludeServlet.class, "/AssertIncludeServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -103,9 +125,9 @@ public class DispatcherTest @Test public void testForwardThenInclude() throws Exception { - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(AssertForwardIncludeServlet.class, "/AssertForwardIncludeServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(AssertForwardIncludeServlet.class, "/AssertForwardIncludeServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -120,9 +142,9 @@ public class DispatcherTest @Test public void testIncludeThenForward() throws Exception { - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*"); String expected= @@ -140,8 +162,8 @@ public class DispatcherTest @Test public void testServletForward() throws Exception { - _context.addServlet(DispatchServletServlet.class, "/dispatch/*"); - _context.addServlet(RogerThatServlet.class, "/roger/*"); + _contextHandler.addServlet(DispatchServletServlet.class, "/dispatch/*"); + _contextHandler.addServlet(RogerThatServlet.class, "/roger/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -157,8 +179,8 @@ public class DispatcherTest @Test public void testServletInclude() throws Exception { - _context.addServlet(DispatchServletServlet.class, "/dispatch/*"); - _context.addServlet(RogerThatServlet.class, "/roger/*"); + _contextHandler.addServlet(DispatchServletServlet.class, "/dispatch/*"); + _contextHandler.addServlet(RogerThatServlet.class, "/roger/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -171,6 +193,80 @@ public class DispatcherTest assertEquals(expected, responses); } + @Test + public void testWorkingResourceHandler() throws Exception + { + String responses = _connector.getResponses("GET /resource/content.txt HTTP/1.0\n" + "Host: localhost\n\n"); + + assertTrue(responses.contains("content goes here")); // from inside the context.txt file + } + + @Test + public void testIncludeToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + Assert.assertNotNull(responses); + + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testForwardToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testWrappedIncludeToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testWrappedForwardToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testForwardFilterToRogerServlet() throws Exception + { + _contextHandler.addServlet(RogerThatServlet.class, "/*"); + _contextHandler.addServlet(ReserveEchoServlet.class,"/recho/*"); + _contextHandler.addServlet(EchoServlet.class, "/echo/*"); + _contextHandler.addFilter(ForwardFilter.class, "/*", FilterMapping.REQUEST); + + String rogerResponse = _connector.getResponses("GET /context/ HTTP/1.0\n" + "Host: localhost\n\n"); + + String echoResponse = _connector.getResponses("GET /context/foo?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n"); + + String rechoResponse = _connector.getResponses("GET /context/?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n"); + + assertTrue(rogerResponse.contains("Roger That!")); + assertTrue(echoResponse.contains("echoText")); + assertTrue(rechoResponse.contains("txeTohce")); + } + + public static class ForwardServlet extends HttpServlet implements Servlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException @@ -187,6 +283,59 @@ public class DispatcherTest } } + /* + * Forward filter works with roger, echo and reverse echo servlets to test various + * forwarding bits using filters. + * + * when there is an echo parameter and the path info is / it forwards to the reverse echo + * anything else in the pathInfo and it sends straight to the echo servlet...otherwise its + * all roger servlet + */ + public static class ForwardFilter implements Filter + { + ServletContext servletContext; + + public void init(FilterConfig filterConfig) throws ServletException + { + servletContext = filterConfig.getServletContext().getContext("/context"); + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + + if ( servletContext == null || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) + { + chain.doFilter(request,response); + return; + } + + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse)response; + + if ( req.getParameter("echo") != null && "/".equals(req.getPathInfo())) + { + RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/recho"); + dispatcher.forward(request,response); + } + else if ( req.getParameter("echo") != null ) + { + RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/echo"); + dispatcher.forward(request,response); + } + else + { + chain.doFilter(request,response); + return; + } + } + + public void destroy() + { + + } + } + + public static class DispatchServletServlet extends HttpServlet implements Servlet { @Override @@ -230,6 +379,84 @@ public class DispatcherTest } } + public static class EchoServlet extends GenericServlet + { + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + String echoText = req.getParameter("echo"); + + if ( echoText == null ) + { + throw new ServletException("echo is a required parameter"); + } + else + { + res.getWriter().print(echoText); + } + } + } + + public static class ReserveEchoServlet extends GenericServlet + { + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + String echoText = req.getParameter("echo"); + + if ( echoText == null ) + { + throw new ServletException("echo is a required parameter"); + } + else + { + res.getWriter().print(new StringBuffer(echoText).reverse().toString()); + } + } + } + + public static class DispatchToResourceServlet extends HttpServlet implements Servlet + { + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException + { + ServletContext targetContext = getServletConfig().getServletContext().getContext("/resource"); + + RequestDispatcher dispatcher = targetContext.getRequestDispatcher(req.getPathInfo()); + + if ( "true".equals(req.getParameter("wrapped"))) + { + if (req.getParameter("do").equals("forward")) + { + dispatcher.forward(new HttpServletRequestWrapper(req),new HttpServletResponseWrapper(res)); + } + else if (req.getParameter("do").equals("include")) + { + dispatcher.include(new HttpServletRequestWrapper(req),new HttpServletResponseWrapper(res)); + } + else + { + throw new ServletException("type of forward or include is required"); + } + } + else + { + if (req.getParameter("do").equals("forward")) + { + dispatcher.forward(req,res); + } + else if (req.getParameter("do").equals("include")) + { + dispatcher.include(req,res); + } + else + { + throw new ServletException("type of forward or include is required"); + } + } + } + } + public static class AssertForwardServlet extends HttpServlet implements Servlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java index 1456e80cb53..ae9da69e67a 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java @@ -13,7 +13,10 @@ package org.eclipse.jetty.servlet; +import static org.junit.Assert.assertEquals; + import java.io.IOException; + import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -26,8 +29,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; - /** * */ diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java index 7154d32acaa..d775512cb7f 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.servlet; import junit.framework.AssertionFailedError; + import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.StatisticsHandler; diff --git a/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt b/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt new file mode 100644 index 00000000000..815d4403267 --- /dev/null +++ b/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt @@ -0,0 +1 @@ +content goes here \ No newline at end of file diff --git a/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt b/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt new file mode 100644 index 00000000000..f3a34851d44 --- /dev/null +++ b/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml index 68ca7504cd6..9e112abff9e 100644 --- a/jetty-servlets/pom.xml +++ b/jetty-servlets/pom.xml @@ -3,7 +3,7 @@ jetty-project org.eclipse.jetty - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-servlets diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java index 0c6fe30bbc8..8df44021880 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.servlets; @@ -34,27 +34,26 @@ import org.eclipse.jetty.util.log.Logger; //----------------------------------------------------------------------------- /** * CGI Servlet. - * - * The cgi bin directory can be set with the "cgibinResourceBase" init parameter - * or it will default to the resource base of the context. - * - * The "commandPrefix" init parameter may be used to set a prefix to all - * commands passed to exec. This can be used on systems that need assistance to - * execute a particular file type. For example on windows this can be set to - * "perl" so that perl scripts are executed. - * - * The "Path" init param is passed to the exec environment as PATH. Note: Must - * be run unpacked somewhere in the filesystem. - * - * Any initParameter that starts with ENV_ is used to set an environment - * variable with the name stripped of the leading ENV_ and using the init - * parameter value. - * - * - * + *

    + * The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the + * "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work. + *
    + * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files. + *

    + * The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a + * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed. + *

    + * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem. + *

    + * Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value. */ public class CGI extends HttpServlet { + /** + * + */ + private static final long serialVersionUID = -6182088932884791073L; + private static final Logger LOG = Log.getLogger(CGI.class); private boolean _ok; @@ -63,81 +62,89 @@ public class CGI extends HttpServlet private String _cmdPrefix; private EnvList _env; private boolean _ignoreExitState; + private boolean _relative; /* ------------------------------------------------------------ */ + @Override public void init() throws ServletException { - _env=new EnvList(); - _cmdPrefix=getInitParameter("commandPrefix"); + _env = new EnvList(); + _cmdPrefix = getInitParameter("commandPrefix"); + _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative")); - String tmp=getInitParameter("cgibinResourceBase"); - if (tmp==null) + String tmp = getInitParameter("cgibinResourceBase"); + if (tmp == null) { - tmp=getInitParameter("resourceBase"); - if (tmp==null) - tmp=getServletContext().getRealPath("/"); + tmp = getInitParameter("resourceBase"); + if (tmp == null) + tmp = getServletContext().getRealPath("/"); + } + else if (_relative) + { + tmp = getServletContext().getRealPath(tmp); } - if (tmp==null) + if (tmp == null) { LOG.warn("CGI: no CGI bin !"); return; } - File dir=new File(tmp); + File dir = new File(tmp); if (!dir.exists()) { - LOG.warn("CGI: CGI bin does not exist - "+dir); + LOG.warn("CGI: CGI bin does not exist - " + dir); return; } if (!dir.canRead()) { - LOG.warn("CGI: CGI bin is not readable - "+dir); + LOG.warn("CGI: CGI bin is not readable - " + dir); return; } if (!dir.isDirectory()) { - LOG.warn("CGI: CGI bin is not a directory - "+dir); + LOG.warn("CGI: CGI bin is not a directory - " + dir); return; } try { - _docRoot=dir.getCanonicalFile(); + _docRoot = dir.getCanonicalFile(); } catch (IOException e) { - LOG.warn("CGI: CGI bin failed - "+dir,e); + LOG.warn("CGI: CGI bin failed - " + dir,e); return; } - _path=getInitParameter("Path"); - if (_path!=null) + _path = getInitParameter("Path"); + if (_path != null) _env.set("PATH",_path); - _ignoreExitState="true".equalsIgnoreCase(getInitParameter("ignoreExitState")); - Enumeration e=getInitParameterNames(); + _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState")); + Enumeration e = getInitParameterNames(); while (e.hasMoreElements()) { - String n=(String)e.nextElement(); - if (n!=null&&n.startsWith("ENV_")) + String n = (String)e.nextElement(); + if (n != null && n.startsWith("ENV_")) _env.set(n.substring(4),getInitParameter(n)); } - if(!_env.envMap.containsKey("SystemRoot")) + if (!_env.envMap.containsKey("SystemRoot")) { - String os = System.getProperty("os.name"); - if (os!=null && os.toLowerCase().indexOf("windows")!=-1) + String os = System.getProperty("os.name"); + if (os != null && os.toLowerCase().indexOf("windows") != -1) { - _env.set("SystemRoot", "C:\\WINDOWS"); + _env.set("SystemRoot","C:\\WINDOWS"); } - } - - _ok=true; + } + + _ok = true; } /* ------------------------------------------------------------ */ + @Override public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { if (!_ok) @@ -145,39 +152,38 @@ public class CGI extends HttpServlet res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); return; } - - String pathInContext=StringUtil.nonNull(req.getServletPath())+StringUtil.nonNull(req.getPathInfo()); + String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo()); if (LOG.isDebugEnabled()) { - LOG.debug("CGI: ContextPath : "+req.getContextPath()); - LOG.debug("CGI: ServletPath : "+req.getServletPath()); - LOG.debug("CGI: PathInfo : "+req.getPathInfo()); - LOG.debug("CGI: _docRoot : "+_docRoot); - LOG.debug("CGI: _path : "+_path); - LOG.debug("CGI: _ignoreExitState: "+_ignoreExitState); + LOG.debug("CGI: ContextPath : " + req.getContextPath()); + LOG.debug("CGI: ServletPath : " + req.getServletPath()); + LOG.debug("CGI: PathInfo : " + req.getPathInfo()); + LOG.debug("CGI: _docRoot : " + _docRoot); + LOG.debug("CGI: _path : " + _path); + LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState); } // pathInContext may actually comprises scriptName/pathInfo...We will // walk backwards up it until we find the script - the rest must // be the pathInfo; - String both=pathInContext; - String first=both; - String last=""; + String both = pathInContext; + String first = both; + String last = ""; - File exe=new File(_docRoot,first); + File exe = new File(_docRoot,first); - while ((first.endsWith("/")||!exe.exists())&&first.length()>=0) + while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0) { - int index=first.lastIndexOf('/'); + int index = first.lastIndexOf('/'); - first=first.substring(0,index); - last=both.substring(index,both.length()); - exe=new File(_docRoot,first); + first = first.substring(0,index); + last = both.substring(index,both.length()); + exe = new File(_docRoot,first); } - if (first.length()==0||!exe.exists()||exe.isDirectory()||!exe.getCanonicalPath().equals(exe.getAbsolutePath())) + if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath())) { res.sendError(404); } @@ -185,8 +191,8 @@ public class CGI extends HttpServlet { if (LOG.isDebugEnabled()) { - LOG.debug("CGI: script is "+exe); - LOG.debug("CGI: pathInfo is "+last); + LOG.debug("CGI: script is " + exe); + LOG.debug("CGI: pathInfo is " + last); } exec(exe,last,req,res); } @@ -198,19 +204,19 @@ public class CGI extends HttpServlet */ private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException { - String path=command.getAbsolutePath(); - File dir=command.getParentFile(); - String scriptName=req.getRequestURI().substring(0,req.getRequestURI().length()-pathInfo.length()); - String scriptPath=getServletContext().getRealPath(scriptName); - String pathTranslated=req.getPathTranslated(); + String path = command.getAbsolutePath(); + File dir = command.getParentFile(); + String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length()); + String scriptPath = getServletContext().getRealPath(scriptName); + String pathTranslated = req.getPathTranslated(); - int len=req.getContentLength(); - if (len<0) - len=0; - if ((pathTranslated==null)||(pathTranslated.length()==0)) - pathTranslated=path; + int len = req.getContentLength(); + if (len < 0) + len = 0; + if ((pathTranslated == null) || (pathTranslated.length() == 0)) + pathTranslated = path; - EnvList env=new EnvList(_env); + EnvList env = new EnvList(_env); // these ones are from "The WWW Common Gateway Interface Version 1.1" // look at : // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1 @@ -218,7 +224,7 @@ public class CGI extends HttpServlet env.set("CONTENT_LENGTH",Integer.toString(len)); env.set("CONTENT_TYPE",req.getContentType()); env.set("GATEWAY_INTERFACE","CGI/1.1"); - if ((pathInfo!=null)&&(pathInfo.length()>0)) + if ((pathInfo != null) && (pathInfo.length() > 0)) { env.set("PATH_INFO",pathInfo); } @@ -240,12 +246,12 @@ public class CGI extends HttpServlet env.set("SERVER_PROTOCOL",req.getProtocol()); env.set("SERVER_SOFTWARE",getServletContext().getServerInfo()); - Enumeration enm=req.getHeaderNames(); + Enumeration enm = req.getHeaderNames(); while (enm.hasMoreElements()) { - String name=(String)enm.nextElement(); - String value=req.getHeader(name); - env.set("HTTP_"+name.toUpperCase().replace('-','_'),value); + String name = (String)enm.nextElement(); + String value = req.getHeader(name); + env.set("HTTP_" + name.toUpperCase().replace('-','_'),value); } // these extra ones were from printenv on www.dev.nomura.co.uk @@ -257,28 +263,28 @@ public class CGI extends HttpServlet // are we meant to decode args here ? or does the script get them // via PATH_INFO ? if we are, they should be decoded and passed // into exec here... - String execCmd=path; - if ((execCmd.charAt(0)!='"')&&(execCmd.indexOf(" ")>=0)) - execCmd="\""+execCmd+"\""; - if (_cmdPrefix!=null) - execCmd=_cmdPrefix+" "+execCmd; + String execCmd = path; + if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0)) + execCmd = "\"" + execCmd + "\""; + if (_cmdPrefix != null) + execCmd = _cmdPrefix + " " + execCmd; - Process p=(dir==null)?Runtime.getRuntime().exec(execCmd,env.getEnvArray()):Runtime.getRuntime().exec(execCmd,env.getEnvArray(),dir); + Process p = (dir == null)?Runtime.getRuntime().exec(execCmd,env.getEnvArray()):Runtime.getRuntime().exec(execCmd,env.getEnvArray(),dir); // hook processes input to browser's output (async) - final InputStream inFromReq=req.getInputStream(); - final OutputStream outToCgi=p.getOutputStream(); - final int inLength=len; + final InputStream inFromReq = req.getInputStream(); + final OutputStream outToCgi = p.getOutputStream(); + final int inLength = len; IO.copyThread(p.getErrorStream(),System.err); - + new Thread(new Runnable() { public void run() { try { - if (inLength>0) + if (inLength > 0) IO.copy(inFromReq,outToCgi,inLength); outToCgi.close(); } @@ -296,28 +302,28 @@ public class CGI extends HttpServlet { // read any headers off the top of our input stream // NOTE: Multiline header items not supported! - String line=null; - InputStream inFromCgi=p.getInputStream(); + String line = null; + InputStream inFromCgi = p.getInputStream(); - //br=new BufferedReader(new InputStreamReader(inFromCgi)); - //while ((line=br.readLine())!=null) - while( (line = getTextLineFromStream( inFromCgi )).length() > 0 ) + // br=new BufferedReader(new InputStreamReader(inFromCgi)); + // while ((line=br.readLine())!=null) + while ((line = getTextLineFromStream(inFromCgi)).length() > 0) { if (!line.startsWith("HTTP")) { - int k=line.indexOf(':'); - if (k>0) + int k = line.indexOf(':'); + if (k > 0) { - String key=line.substring(0,k).trim(); - String value = line.substring(k+1).trim(); + String key = line.substring(0,k).trim(); + String value = line.substring(k + 1).trim(); if ("Location".equals(key)) { res.sendRedirect(res.encodeRedirectURL(value)); } else if ("Status".equals(key)) { - String[] token = value.split( " " ); - int status=Integer.parseInt(token[0]); + String[] token = value.split(" "); + int status = Integer.parseInt(token[0]); res.setStatus(status); } else @@ -330,15 +336,15 @@ public class CGI extends HttpServlet } // copy cgi content to response stream... os = res.getOutputStream(); - IO.copy(inFromCgi, os); + IO.copy(inFromCgi,os); p.waitFor(); if (!_ignoreExitState) { - int exitValue=p.exitValue(); - if (0!=exitValue) + int exitValue = p.exitValue(); + if (0 != exitValue) { - LOG.warn("Non-zero exit status ("+exitValue+") from CGI program: "+path); + LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path); if (!res.isCommitted()) res.sendError(500,"Failed to exec CGI"); } @@ -356,13 +362,13 @@ public class CGI extends HttpServlet } finally { - if( os != null ) + if (os != null) { try { os.close(); } - catch(Exception e) + catch (Exception e) { LOG.ignore(e); } @@ -375,19 +381,24 @@ public class CGI extends HttpServlet /** * Utility method to get a line of text from the input stream. - * @param is the input stream + * + * @param is + * the input stream * @return the line of text * @throws IOException */ - private String getTextLineFromStream( InputStream is ) throws IOException { + private String getTextLineFromStream(InputStream is) throws IOException + { StringBuilder buffer = new StringBuilder(); int b; - while( (b = is.read()) != -1 && b != (int) '\n' ) { - buffer.append( (char) b ); - } - return buffer.toString().trim(); + while ((b = is.read()) != -1 && b != '\n') + { + buffer.append((char)b); + } + return buffer.toString().trim(); } + /* ------------------------------------------------------------ */ /** * private utility class that manages the Environment passed to exec. @@ -398,12 +409,12 @@ public class CGI extends HttpServlet EnvList() { - envMap=new HashMap(); + envMap = new HashMap(); } EnvList(EnvList l) { - envMap=new HashMap(l.envMap); + envMap = new HashMap(l.envMap); } /** @@ -411,7 +422,7 @@ public class CGI extends HttpServlet */ public void set(String name, String value) { - envMap.put(name,name+"="+StringUtil.nonNull(value)); + envMap.put(name,name + "=" + StringUtil.nonNull(value)); } /** Get representation suitable for passing to exec. */ @@ -420,6 +431,7 @@ public class CGI extends HttpServlet return (String[])envMap.values().toArray(new String[envMap.size()]); } + @Override public String toString() { return envMap.toString(); diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java index 0bec5df794e..9b2b659dcff 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java @@ -18,7 +18,7 @@ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -37,7 +37,7 @@ public class CloseableDoSFilter extends DoSFilter { try { - Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); + Request base_request=(request instanceof Request)?(Request)request:AbstractHttpConnection.getCurrentConnection().getRequest(); base_request.getConnection().getEndPoint().close(); } catch(IOException e) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index 67657cfad0a..f4026369372 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -32,7 +33,7 @@ import org.eclipse.jetty.util.log.Logger; /** *

    Implementation of the - * cross-origin resource sharing.

    + * cross-origin resource sharing.

    *

    A typical example is to use this filter to allow cross-domain * cometd communication using the standard * long polling transport instead of the JSONP transport (that is less @@ -78,34 +79,35 @@ public class CrossOriginFilter implements Filter // Request headers private static final String ORIGIN_HEADER = "Origin"; - private static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method"; - private static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers"; + public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method"; + public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers"; // Response headers - private static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin"; - private static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods"; - private static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; - private static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age"; - private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin"; + public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods"; + public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; + public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age"; + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials"; // Implementation constants - private static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins"; - private static final String ALLOWED_METHODS_PARAM = "allowedMethods"; - private static final String ALLOWED_HEADERS_PARAM = "allowedHeaders"; - private static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge"; - private static final String ALLOWED_CREDENTIALS_PARAM = "allowCredentials"; + public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins"; + public static final String ALLOWED_METHODS_PARAM = "allowedMethods"; + public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders"; + public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge"; + public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials"; private static final String ANY_ORIGIN = "*"; private static final List SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD"); - private boolean anyOriginAllowed = false; + private boolean anyOriginAllowed; private List allowedOrigins = new ArrayList(); private List allowedMethods = new ArrayList(); private List allowedHeaders = new ArrayList(); private int preflightMaxAge = 0; - private boolean allowCredentials = true; + private boolean allowCredentials; public void init(FilterConfig config) throws ServletException { String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM); - if (allowedOriginsConfig == null) allowedOriginsConfig = "*"; + if (allowedOriginsConfig == null) + allowedOriginsConfig = "*"; String[] allowedOrigins = allowedOriginsConfig.split(","); for (String allowedOrigin : allowedOrigins) { @@ -126,15 +128,18 @@ public class CrossOriginFilter implements Filter } String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM); - if (allowedMethodsConfig == null) allowedMethodsConfig = "GET,POST"; + if (allowedMethodsConfig == null) + allowedMethodsConfig = "GET,POST,HEAD"; allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(","))); String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM); - if (allowedHeadersConfig == null) allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin"; + if (allowedHeadersConfig == null) + allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin"; allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(","))); String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM); - if (preflightMaxAgeConfig == null) preflightMaxAgeConfig = "1800"; // Default is 30 minutes + if (preflightMaxAgeConfig == null) + preflightMaxAgeConfig = "1800"; // Default is 30 minutes try { preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig); @@ -144,16 +149,20 @@ public class CrossOriginFilter implements Filter LOG.info("Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig); } - String allowedCredentialsConfig = config.getInitParameter(ALLOWED_CREDENTIALS_PARAM); - if (allowedCredentialsConfig == null) allowedCredentialsConfig = "false"; + String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM); + if (allowedCredentialsConfig == null) + allowedCredentialsConfig = "true"; allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig); - LOG.debug("Cross-origin filter configuration: " + - ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " + - ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " + - ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " + - PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " + - ALLOWED_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig); + if (LOG.isDebugEnabled()) + { + LOG.debug("Cross-origin filter configuration: " + + ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " + + ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " + + ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " + + PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " + + ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig); + } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException @@ -174,11 +183,16 @@ public class CrossOriginFilter implements Filter LOG.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI()); handleSimpleResponse(request, response, origin); } - else + else if (isPreflightRequest(request)) { LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI()); handlePreflightResponse(request, response, origin); } + else + { + LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI()); + handleSimpleResponse(request, response, origin); + } } else { @@ -201,14 +215,33 @@ public class CrossOriginFilter implements Filter return true; } - private boolean originMatches(String origin) + private boolean originMatches(String originList) { - if (anyOriginAllowed) return true; - for (String allowedOrigin : allowedOrigins) + if (anyOriginAllowed) + return true; + + if (originList.trim().length() == 0) + return false; + + String[] origins = originList.split(" "); + for (String origin : origins) { - if (allowedOrigin.equals(origin)) return true; + if (origin.trim().length() == 0) + continue; + + boolean allowed = false; + for (String allowedOrigin : allowedOrigins) + { + if (allowedOrigin.equals(origin)) + { + allowed = true; + break; + } + } + if (!allowed) + return false; } - return false; + return true; } private boolean isSimpleRequest(HttpServletRequest request) @@ -216,8 +249,8 @@ public class CrossOriginFilter implements Filter String method = request.getMethod(); if (SIMPLE_HTTP_METHODS.contains(method)) { - // TODO: implement better section 6.1 - // Section 6.1 says that for a request to be simple, custom request headers must be simple. + // TODO: implement better detection of simple headers + // The specification says that for a request to be simple, custom request headers must be simple. // Here for simplicity I just check if there is a Access-Control-Request-Method header, // which is required for preflight requests return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null; @@ -225,30 +258,37 @@ public class CrossOriginFilter implements Filter return false; } + private boolean isPreflightRequest(HttpServletRequest request) + { + String method = request.getMethod(); + if (!"OPTIONS".equalsIgnoreCase(method)) + return false; + if (request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null) + return false; + return true; + } + private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin) { response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); - if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); + if (allowCredentials) + response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); } private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin) { - // Implementation of section 5.2 - - // 5.2.3 and 5.2.5 boolean methodAllowed = isMethodAllowed(request); - if (!methodAllowed) return; - // 5.2.4 and 5.2.6 + if (!methodAllowed) + return; boolean headersAllowed = areHeadersAllowed(request); - if (!headersAllowed) return; - // 5.2.7 + if (!headersAllowed) + return; response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); - if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); - // 5.2.8 - if (preflightMaxAge > 0) response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge)); - // 5.2.9 + if (allowCredentials) + response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); + if (preflightMaxAge > 0) + response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge)); response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods)); - // 5.2.10 response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders)); } @@ -258,9 +298,7 @@ public class CrossOriginFilter implements Filter LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod); boolean result = false; if (accessControlRequestMethod != null) - { result = allowedMethods.contains(accessControlRequestMethod); - } LOG.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}", accessControlRequestMethod, allowedMethods); return result; } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index 51c94694459..6c12e2e75b3 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -14,13 +14,16 @@ package org.eclipse.jetty.servlets; import java.io.IOException; +import java.io.Serializable; import java.util.HashSet; +import java.util.Map; import java.util.Queue; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -31,8 +34,10 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionEvent; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationListener; @@ -907,13 +912,14 @@ public class DoSFilter implements Filter * A RateTracker is associated with a connection, and stores request rate * data. */ - class RateTracker extends Timeout.Task implements HttpSessionBindingListener + class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener { - protected final String _id; - protected final int _type; - protected final long[] _timestamps; - protected int _next; - + transient protected final String _id; + transient protected final int _type; + transient protected final long[] _timestamps; + transient protected int _next; + + public RateTracker(String id, int type,int maxRequestsPerSecond) { _id = id; @@ -952,25 +958,49 @@ public class DoSFilter implements Filter public void valueBound(HttpSessionBindingEvent event) - { + { + if (LOG.isDebugEnabled()) + LOG.debug("Value bound:"+_id); } public void valueUnbound(HttpSessionBindingEvent event) { - _rateTrackers.remove(_id); + //take the tracker out of the list of trackers + if (_rateTrackers != null) + _rateTrackers.remove(_id); + if (LOG.isDebugEnabled()) LOG.debug("Tracker removed: "+_id); } + public void sessionWillPassivate(HttpSessionEvent se) + { + //take the tracker of the list of trackers (if its still there) + //and ensure that we take ourselves out of the session so we are not saved + if (_rateTrackers != null) + _rateTrackers.remove(_id); + se.getSession().removeAttribute(__TRACKER); + if (LOG.isDebugEnabled()) LOG.debug("Value removed: "+_id); + } + + public void sessionDidActivate(HttpSessionEvent se) + { + LOG.warn("Unexpected session activation"); + } + + public void expired() { - long now = _trackerTimeoutQ.getNow(); - int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length; - long last=_timestamps[latestIndex]; - boolean hasRecentRequest = last != 0 && (now-last)<1000L; + if (_rateTrackers != null && _trackerTimeoutQ != null) + { + long now = _trackerTimeoutQ.getNow(); + int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length; + long last=_timestamps[latestIndex]; + boolean hasRecentRequest = last != 0 && (now-last)<1000L; - if (hasRecentRequest) - reschedule(); - else - _rateTrackers.remove(_id); + if (hasRecentRequest) + reschedule(); + else + _rateTrackers.remove(_id); + } } @Override @@ -978,6 +1008,8 @@ public class DoSFilter implements Filter { return "RateTracker/"+_id+"/"+_type; } + + } class FixedRateTracker extends RateTracker diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java index 0084ddaf861..7d9a24ac58a 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java @@ -67,10 +67,10 @@ public class IncludableGzipFilter extends GzipFilter public IncludableResponseWrapper(HttpServletRequest request, HttpServletResponse response) { super(request,response); - - _mimeTypes = IncludableGzipFilter.this._mimeTypes; - _bufferSize = IncludableGzipFilter.this._bufferSize; - _minGzipSize = IncludableGzipFilter.this._minGzipSize; + + super.setMimeTypes(IncludableGzipFilter.this._mimeTypes); + super.setBufferSize(IncludableGzipFilter.this._bufferSize); + super.setMinGzipSize(IncludableGzipFilter.this._minGzipSize); } @Override diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java index 4111a1de6ff..049f37072b1 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java @@ -63,6 +63,7 @@ import org.eclipse.jetty.util.TypeUtil; */ public class MultiPartFilter implements Filter { + public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType"; private final static String FILES ="org.eclipse.jetty.servlet.MultiPartFilter.files"; private File tempdir; private boolean _deleteFiles; @@ -132,8 +133,11 @@ public class MultiPartFilter implements Filter String content_disposition=null; String content_transfer_encoding=null; + outer:while(!lastPart) { + String type_content=null; + while(true) { // read a line @@ -155,7 +159,9 @@ public class MultiPartFilter implements Filter if(key.equals("content-disposition")) content_disposition=value; else if(key.equals("content-transfer-encoding")) - content_transfer_encoding=value; + content_transfer_encoding=value; + else if (key.equals("content-type")) + type_content = value; } } // Extract content-disposition @@ -207,6 +213,8 @@ public class MultiPartFilter implements Filter out = new BufferedOutputStream(out, _fileOutputBuffer); request.setAttribute(name,file); params.add(name, filename); + if (type_content != null) + params.add(name+CONTENT_TYPE_SUFFIX, type_content); if (_deleteFiles) { @@ -330,6 +338,8 @@ public class MultiPartFilter implements Filter { bytes = ((ByteArrayOutputStream)out).toByteArray(); params.add(name,bytes); + if (type_content != null) + params.add(name+CONTENT_TYPE_SUFFIX, type_content); } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java index c506a82c0e4..2b186b49677 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.StringTokenizer; + import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -38,7 +39,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationSupport; @@ -49,15 +49,11 @@ import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.servlet.Holder; import org.eclipse.jetty.util.HostMap; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.omg.CORBA._PolicyStub; /** * Asynchronous Proxy Servlet. diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java index c2def316972..3774a28bf8f 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java @@ -1,7 +1,11 @@ package org.eclipse.jetty.servlets; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.net.Socket; + import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletException; @@ -18,9 +22,6 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * @version $Revision$ $Date$ */ diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java index 10b93271858..252783d68e8 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.servlets; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java new file mode 100644 index 00000000000..e59051ffbf0 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java @@ -0,0 +1,343 @@ +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.testing.ServletTester; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CrossOriginFilterTest +{ + private ServletTester tester; + + @Before + public void init() throws Exception + { + tester = new ServletTester(); + tester.start(); + } + + @After + public void destroy() throws Exception + { + if (tester != null) + tester.stop(); + } + + @Test + public void testRequestWithNoOriginArrivesToApplication() throws Exception + { + tester.getContext().addFilter(CrossOriginFilter.class, "/*", FilterMapping.DEFAULT); + + final CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testSimpleRequestWithNonMatchingOrigin() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + String origin = "http://localhost"; + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String otherOrigin = origin.replace("localhost", "127.0.0.1"); + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: " + otherOrigin + "\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testSimpleRequestWithMatchingOrigin() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + String origin = "http://localhost"; + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: " + origin + "\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testSimpleRequestWithMatchingMultipleOrigins() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + String origin = "http://localhost"; + String otherOrigin = origin.replace("localhost", "127.0.0.1"); + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin + "," + otherOrigin); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + // Use 2 spaces as separator to test that the implementation does not fail + "Origin: " + otherOrigin + " " + " " + origin + "\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testSimpleRequestWithoutCredentials() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "false"); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testNonSimpleRequestWithoutPreflight() throws Exception + { + // We cannot know if an actual request has performed the preflight before: + // we'll trust browsers to do it right, so responses to actual requests + // will contain the CORS response headers. + + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "PUT / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testOptionsRequestButNotPreflight() throws Exception + { + // We cannot know if an actual request has performed the preflight before: + // we'll trust browsers to do it right, so responses to actual requests + // will contain the CORS response headers. + + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "OPTIONS / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + @Test + public void testPUTRequestWithPreflight() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "PUT"); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + // Preflight request + String request = "" + + "OPTIONS / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": PUT\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_MAX_AGE_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Preflight request was ok, now make the actual request + request = "" + + "PUT / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + } + + @Test + public void testDELETERequestWithPreflightAndAllowedCustomHeaders() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,HEAD,POST,PUT,DELETE"); + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin,X-Custom"); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + // Preflight request + String request = "" + + "OPTIONS / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" + + CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_MAX_AGE_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Preflight request was ok, now make the actual request + request = "" + + "DELETE / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "X-Custom: value\r\n" + + "X-Requested-With: local\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + } + + @Test + public void testDELETERequestWithPreflightAndNotAllowedCustomHeaders() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,HEAD,POST,PUT,DELETE"); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + // Preflight request + String request = "" + + "OPTIONS / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" + + CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + // The preflight request failed because header X-Custom is not allowed, actual request not issued + } + + @Test + public void testCrossOriginFilterDisabledForWebSocketUpgrade() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: WebSocket\r\n" + + "Origin: http://localhost\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + + public static class ResourceServlet extends HttpServlet + { + private final CountDownLatch latch; + + public ResourceServlet(CountDownLatch latch) + { + this.latch = latch; + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + latch.countDown(); + } + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java index 1b83855aa1a..109fe2675f2 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java @@ -1,12 +1,12 @@ package org.eclipse.jetty.servlets; +import java.io.File; import java.util.Arrays; import java.util.List; - import javax.servlet.Servlet; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.gzip.GzipResponseWrapper; -import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlets.gzip.GzipTester; import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite; @@ -24,7 +24,7 @@ import org.junit.runners.Parameterized.Parameters; /** * Test the GzipFilter support for Content-Length setting variations. - * + * * @see http://bugs.eclipse.org/354014 */ @RunWith(Parameterized.class) @@ -38,9 +38,9 @@ public class GzipFilterContentLengthTest * in different order so as to simulate the real world scenario * that caused the bug in Eclipse http://bugs.eclipse.org/354014 *

    - * This test case will be run with each of the entries in + * This test case will be run with each of the entries in * the array below as setup parameters for the test case. - * + * * @return the junit parameters */ @Parameters @@ -48,7 +48,6 @@ public class GzipFilterContentLengthTest { return Arrays.asList(new Object[][] { - { DefaultServlet.class }, { TestServletLengthStreamTypeWrite.class }, { TestServletLengthTypeStreamWrite.class }, { TestServletStreamLengthTypeWrite.class }, @@ -76,7 +75,7 @@ public class GzipFilterContentLengthTest { GzipTester tester = new GzipTester(testingdir); - tester.prepareServerFile(filename,filesize); + File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize); FilterHolder holder = tester.setContentServlet(testServlet); holder.setInitParameter("mimeTypes","text/plain"); @@ -84,7 +83,7 @@ public class GzipFilterContentLengthTest try { tester.start(); - tester.assertIsResponseGzipCompressed(filename); + tester.assertIsResponseGzipCompressed(testfile.getName()); } finally { @@ -96,7 +95,7 @@ public class GzipFilterContentLengthTest { GzipTester tester = new GzipTester(testingdir); - tester.prepareServerFile(filename,filesize); + File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize); FilterHolder holder = tester.setContentServlet(testServlet); holder.setInitParameter("mimeTypes","text/plain"); @@ -104,7 +103,7 @@ public class GzipFilterContentLengthTest try { tester.start(); - tester.assertIsResponseNotGzipCompressed(filename,filesize); + tester.assertIsResponseNotGzipCompressed(testfile.getName(),filesize,HttpStatus.OK_200); } finally { @@ -118,7 +117,7 @@ public class GzipFilterContentLengthTest @Test public void testIsGzipCompressedSmall() throws Exception { - assertIsGzipCompressed("file.txt",SMALL); + assertIsGzipCompressed("file-small.txt",SMALL); } /** @@ -127,7 +126,7 @@ public class GzipFilterContentLengthTest @Test public void testIsGzipCompressedMedium() throws Exception { - assertIsGzipCompressed("file.txt",MEDIUM); + assertIsGzipCompressed("file-med.txt",MEDIUM); } /** @@ -136,54 +135,54 @@ public class GzipFilterContentLengthTest @Test public void testIsGzipCompressedLarge() throws Exception { - assertIsGzipCompressed("file.txt",LARGE); + assertIsGzipCompressed("file-large.txt",LARGE); } /** - * Tests for problems with Content-Length header on small size files + * Tests for problems with Content-Length header on small size files * that are not being compressed encountered when using GzipFilter - * + * * @see http://bugs.eclipse.org/354014 */ @Test public void testIsNotGzipCompressedTiny() throws Exception { - assertIsNotGzipCompressed("file.txt",TINY); + assertIsNotGzipCompressed("file-tiny.txt",TINY); } /** - * Tests for problems with Content-Length header on small size files + * Tests for problems with Content-Length header on small size files * that are not being compressed encountered when using GzipFilter - * + * * @see http://bugs.eclipse.org/354014 */ @Test public void testIsNotGzipCompressedSmall() throws Exception { - assertIsNotGzipCompressed("file.mp3",SMALL); + assertIsNotGzipCompressed("file-small.mp3",SMALL); } /** - * Tests for problems with Content-Length header on medium size files + * Tests for problems with Content-Length header on medium size files * that are not being compressed encountered when using GzipFilter - * + * * @see http://bugs.eclipse.org/354014 */ @Test public void testIsNotGzipCompressedMedium() throws Exception { - assertIsNotGzipCompressed("file.mp3",MEDIUM); + assertIsNotGzipCompressed("file-medium.mp3",MEDIUM); } /** - * Tests for problems with Content-Length header on large size files + * Tests for problems with Content-Length header on large size files * that were not being compressed encountered when using GzipFilter - * + * * @see http://bugs.eclipse.org/354014 */ @Test public void testIsNotGzipCompressedLarge() throws Exception { - assertIsNotGzipCompressed("file.mp3",LARGE); + assertIsNotGzipCompressed("file-large.mp3",LARGE); } } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java index e597c139b1c..0f12e9f27a6 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java @@ -1,5 +1,13 @@ package org.eclipse.jetty.servlets; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.gzip.GzipResponseWrapper; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.FilterHolder; @@ -13,6 +21,29 @@ import org.junit.Test; */ public class GzipFilterDefaultTest { + + + public static class HttpStatusServlet extends HttpServlet + { + private int _status = 204; + + public HttpStatusServlet() + { + super(); + } + + public void setStatus (int status) + { + _status = status; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setStatus(_status); + } + + } @Rule public TestingDir testingdir = new TestingDir(); @@ -77,11 +108,33 @@ public class GzipFilterDefaultTest try { tester.start(); - tester.assertIsResponseNotGzipCompressed("file.mp3", filesize); + tester.assertIsResponseNotGzipCompressed("file.mp3", filesize, HttpStatus.OK_200); } finally { tester.stop(); } } + + @Test + public void testIsNotGzipCompressedHttpStatus() throws Exception + { + GzipTester tester = new GzipTester(testingdir); + + // Test error code 204 + FilterHolder holder = tester.setContentServlet(HttpStatusServlet.class); + holder.setInitParameter("mimeTypes","text/plain"); + + try + { + tester.start(); + tester.assertIsResponseNotGzipCompressed(null, -1, 204); + } + finally + { + tester.stop(); + } + + } + } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java new file mode 100644 index 00000000000..6e37eb311fb --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java @@ -0,0 +1,85 @@ +// ======================================================================== +// Copyright (c) 2004-2009 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.servlets; + +import javax.servlet.Servlet; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlets.gzip.GzipTester; +import org.eclipse.jetty.servlets.gzip.TestMinGzipSizeServlet; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.junit.Rule; +import org.junit.Test; + +/** + * Perform specific tests on the IncludableGzipFilter's ability to manage + * minGzipSize initialization parameter. + * + * @see http://bugs.eclipse.org/366106 + */ +public class IncludableGzipFilterMinSizeTest +{ + @Rule + public TestingDir testdir = new TestingDir(); + + private Class testServlet = TestMinGzipSizeServlet.class; + + @Test + public void testUnderMinSize() throws Exception + { + GzipTester tester = new GzipTester(testdir); + // Use IncludableGzipFilter + tester.setGzipFilterClass(IncludableGzipFilter.class); + + FilterHolder holder = tester.setContentServlet(testServlet); + // A valid mime type that we will never use in this test. + // configured here to prevent mimeType==null logic + holder.setInitParameter("mimeTypes","application/soap+xml"); + holder.setInitParameter("minGzipSize", "2048"); + holder.setInitParameter("uncheckedPrintWriter","true"); + + tester.copyTestServerFile("small_script.js"); + + try { + tester.start(); + tester.assertIsResponseNotGzipFiltered("small_script.js", + "small_script.js.sha1", + "text/javascript; charset=utf-8"); + } finally { + tester.stop(); + } + } + + @Test + public void testOverMinSize() throws Exception + { + GzipTester tester = new GzipTester(testdir); + // Use IncludableGzipFilter + tester.setGzipFilterClass(IncludableGzipFilter.class); + + FilterHolder holder = tester.setContentServlet(testServlet); + holder.setInitParameter("mimeTypes","application/soap+xml,text/javascript"); + holder.setInitParameter("minGzipSize", "2048"); + holder.setInitParameter("uncheckedPrintWriter","true"); + + tester.copyTestServerFile("big_script.js"); + + try { + tester.start(); + tester.assertIsResponseGzipCompressed("big_script.js"); + } finally { + tester.stop(); + } + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java index 2dc3048024c..0dfd6f17bf8 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.servlets; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; import java.io.File; import java.io.FileInputStream; @@ -39,6 +40,21 @@ public class MultipartFilterTest private File _dir; private ServletTester tester; + + public static class TestServlet extends DumpServlet + { + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + assertNotNull(req.getParameter("fileup")); + assertNotNull(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX)); + assertEquals(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX), "application/octet-stream"); + + super.doPost(req, resp); + } + + } @Before public void setUp() throws Exception { @@ -51,7 +67,7 @@ public class MultipartFilterTest tester=new ServletTester(); tester.setContextPath("/context"); tester.setResourceBase(_dir.getCanonicalPath()); - tester.addServlet(DumpServlet.class, "/"); + tester.addServlet(TestServlet.class, "/"); FilterHolder multipartFilter = tester.addFilter(MultiPartFilter.class,"/*",FilterMapping.DEFAULT); multipartFilter.setInitParameter("deleteFiles", "true"); tester.start(); @@ -93,6 +109,7 @@ public class MultipartFilterTest assertTrue(response.getMethod()==null); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); } + @Test public void testPost() throws Exception @@ -124,6 +141,7 @@ public class MultipartFilterTest assertEquals(HttpServletResponse.SC_OK,response.getStatus()); assertTrue(response.getContent().indexOf("brown cow")>=0); } + @Test public void testEncodedPost() throws Exception diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java index 2b158eb6e87..c92cc007f80 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java @@ -13,6 +13,9 @@ package org.eclipse.jetty.servlets; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; @@ -21,6 +24,7 @@ import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set; + import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.servlet.FilterHolder; @@ -31,9 +35,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class PutFilterTest { private File _dir; diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java index f84b14367f8..85141c21852 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java @@ -12,10 +12,14 @@ // ======================================================================== package org.eclipse.jetty.servlets; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; + import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -34,9 +38,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - public class QoSFilterTest { private static final Logger LOG = Log.getLogger(QoSFilterTest.class); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java index 722e907a20e..25dde7021af 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java @@ -1,8 +1,5 @@ package org.eclipse.jetty.servlets.gzip; -import static org.hamcrest.Matchers.*; - -import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -15,7 +12,6 @@ import java.util.Enumeration; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; - import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; @@ -30,8 +26,15 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + public class GzipTester { + private Class gzipFilterClass = GzipFilter.class; private String encoding = "ISO8859_1"; private ServletTester servletTester; private TestingDir testdir; @@ -40,7 +43,7 @@ public class GzipTester { this.testdir = testingdir; // Make sure we start with a clean testing directory. - this.testdir.ensureEmpty(); + // DOES NOT WORK IN WINDOWS - this.testdir.ensureEmpty(); } public void assertIsResponseGzipCompressed(String filename) throws Exception @@ -105,7 +108,7 @@ public class GzipTester *

    * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be * compressed by the GzipFilter. - * + * * @param requestedFilename * the filename used to on the GET request,. * @param testResourceSha1Sum @@ -188,7 +191,7 @@ public class GzipTester /** * Asserts that the requested filename results in a properly structured GzipFilter response, where the content is * not compressed, and the content-length is returned appropriately. - * + * * @param filename * the filename used for the request, and also used to compare the response to the server file, assumes * that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the @@ -198,7 +201,7 @@ public class GzipTester * passing -1 will disable the Content-Length assertion) * @throws Exception */ - public void assertIsResponseNotGzipCompressed(String filename, int expectedFilesize) throws Exception + public void assertIsResponseNotGzipCompressed(String filename, int expectedFilesize, int status) throws Exception { System.err.printf("[GzipTester] requesting /context/%s%n",filename); HttpTester request = new HttpTester(); @@ -208,7 +211,10 @@ public class GzipTester request.setVersion("HTTP/1.0"); request.setHeader("Host","tester"); request.setHeader("Accept-Encoding","gzip"); - request.setURI("/context/" + filename); + if (filename == null) + request.setURI("/context/"); + else + request.setURI("/context/"+filename); // Issue the request ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes()); @@ -218,7 +224,7 @@ public class GzipTester // Assert the response headers Assert.assertThat("Response.method",response.getMethod(),nullValue()); - Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK)); + Assert.assertThat("Response.status",response.getStatus(),is(status)); if (expectedFilesize != (-1)) { Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue()); @@ -228,31 +234,37 @@ public class GzipTester Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),not(containsString("gzip"))); // Assert that the contents are what we expect. - File serverFile = testdir.getFile(filename); - String expected = IO.readToString(serverFile); - String actual = null; - - InputStream in = null; - ByteArrayOutputStream out = null; - try + if (filename != null) { - in = new ByteArrayInputStream(response.getContentBytes()); - out = new ByteArrayOutputStream(); - IO.copy(in,out); + File serverFile = testdir.getFile(filename); + String expected = IO.readToString(serverFile); + String actual = null; - actual = out.toString(encoding); - Assert.assertEquals("Server contents",expected,actual); - } - finally - { - IO.close(out); - IO.close(in); + InputStream in = null; + ByteArrayOutputStream out = null; + try + { + in = new ByteArrayInputStream(response.getContentBytes()); + out = new ByteArrayOutputStream(); + IO.copy(in,out); + + actual = out.toString(encoding); + Assert.assertEquals("Server contents",expected,actual); + } + finally + { + IO.close(out); + IO.close(in); + } } } + + + /** * Generate string content of arbitrary length. - * + * * @param length * the length of the string to generate. * @return the string content. @@ -293,37 +305,56 @@ public class GzipTester /** * Create a file on the server resource path of a specified filename and size. - * + * * @param filename * the filename to create * @param filesize * the file size to create (Note: this isn't suitable for creating large multi-megabyte files) */ - public void prepareServerFile(String filename, int filesize) throws IOException + public File prepareServerFile(String filename, int filesize) throws IOException { - File testFile = testdir.getFile(filename); + File dir = testdir.getDir(); + File testFile = new File(dir,filename); + // Make sure we have a uniq filename (to work around windows File.delete bug) + int i = 0; + while (testFile.exists()) + { + testFile = new File(dir,(i++) + "-" + filename); + } FileOutputStream fos = null; - BufferedOutputStream out = null; ByteArrayInputStream in = null; try { fos = new FileOutputStream(testFile,false); - out = new BufferedOutputStream(fos); in = new ByteArrayInputStream(generateContent(filesize).getBytes(encoding)); - IO.copy(in,out); + IO.copy(in,fos); + return testFile; } finally { IO.close(in); - IO.close(out); IO.close(fos); } } + /** + * Copy a src/test/resource file into the server tree for eventual serving. + * + * @param filename + * the filename to look for in src/test/resources + */ + public void copyTestServerFile(String filename) throws IOException + { + File srcFile = MavenTestingUtils.getTestResourceFile(filename); + File testFile = testdir.getFile(filename); + + IO.copy(srcFile,testFile); + } + /** * Set the servlet that provides content for the GzipFilter in being tested. - * + * * @param servletClass * the servlet that will provide content. * @return the FilterHolder for configuring the GzipFilter's initParameters with @@ -335,10 +366,20 @@ public class GzipTester servletTester.setResourceBase(testdir.getDir().getCanonicalPath()); ServletHolder servletHolder = servletTester.addServlet(servletClass,"/"); servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath()); - FilterHolder holder = servletTester.addFilter(GzipFilter.class,"/*",0); + FilterHolder holder = servletTester.addFilter(gzipFilterClass,"/*",0); return holder; } + public Class getGzipFilterClass() + { + return gzipFilterClass; + } + + public void setGzipFilterClass(Class gzipFilterClass) + { + this.gzipFilterClass = gzipFilterClass; + } + public void setEncoding(String encoding) { this.encoding = encoding; diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java new file mode 100644 index 00000000000..3a3c9897f6c --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java @@ -0,0 +1,52 @@ +package org.eclipse.jetty.servlets.gzip; + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; + +/** + * Test servlet for testing against unusual minGzip configurable. + */ +@SuppressWarnings("serial") +public class TestMinGzipSizeServlet extends TestDirContentServlet +{ + private MimeTypes mimeTypes; + + @Override + public void init(ServletConfig config) throws ServletException + { + super.init(config); + mimeTypes = new MimeTypes(); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String fileName = request.getServletPath(); + byte[] dataBytes = loadContentFileBytes(fileName); + + response.setContentLength(dataBytes.length); + if (fileName.endsWith(".js")) + { + // intentionally long-form content type to test ";" splitting in code + response.setContentType("text/javascript; charset=utf-8"); + } + else + { + Buffer buf = mimeTypes.getMimeByExtension(fileName); + if (buf != null) + { + response.setContentType(buf.toString()); + } + } + ServletOutputStream out = response.getOutputStream(); + out.write(dataBytes); + } +} diff --git a/jetty-servlets/src/test/resources/big_script.js b/jetty-servlets/src/test/resources/big_script.js new file mode 100644 index 00000000000..938a413c3fd --- /dev/null +++ b/jetty-servlets/src/test/resources/big_script.js @@ -0,0 +1,792 @@ +//---------------------------------------------------------------------- +// +// Silly / Pointless Javascript to test GZIP compression. +// +//---------------------------------------------------------------------- + +var LOGO = { + dat: [ + 0x50, 0x89, 0x47, 0x4e, 0x0a, 0x0d, 0x0a, 0x1a, 0x00, 0x00, 0x0d, 0x00, 0x48, 0x49, 0x52, 0x44, + 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x78, 0x00, 0x06, 0x08, 0x00, 0x00, 0x2a, 0x00, 0x21, 0x96, + 0x00, 0x0f, 0x00, 0x00, 0x73, 0x04, 0x49, 0x42, 0x08, 0x54, 0x08, 0x08, 0x7c, 0x08, 0x64, 0x08, + 0x00, 0x88, 0x00, 0x00, 0x70, 0x09, 0x59, 0x48, 0x00, 0x73, 0x04, 0x00, 0x00, 0x27, 0x04, 0x00, + 0x01, 0x27, 0x4f, 0xd9, 0x80, 0x1d, 0x00, 0x00, 0x19, 0x00, 0x45, 0x74, 0x74, 0x58, 0x6f, 0x53, + 0x74, 0x66, 0x61, 0x77, 0x65, 0x72, 0x77, 0x00, 0x77, 0x77, 0x69, 0x2e, 0x6b, 0x6e, 0x63, 0x73, + 0x70, 0x61, 0x2e, 0x65, 0x72, 0x6f, 0x9b, 0x67, 0x3c, 0xee, 0x00, 0x1a, 0x20, 0x00, 0x49, 0x00, + 0x41, 0x44, 0x78, 0x54, 0xed, 0x9c, 0x79, 0x9d, 0x1c, 0xd8, 0x95, 0x55, 0x3f, 0xff, 0xfb, 0xa7, + 0xb2, 0xdd, 0x24, 0xef, 0x24, 0x81, 0x81, 0x2c, 0x20, 0x40, 0xd5, 0x91, 0x8b, 0xb0, 0x3f, 0xbb, + 0x1d, 0x04, 0x54, 0x1c, 0x46, 0x74, 0x17, 0x18, 0xd1, 0x98, 0x19, 0xd1, 0x05, 0x11, 0x37, 0xf4, + 0xe2, 0x8f, 0xcc, 0xb8, 0x28, 0x8c, 0x82, 0x02, 0x22, 0x8c, 0xe0, 0xe8, 0xe2, 0x86, 0x02, 0x88, + 0x8e, 0xa2, 0x08, 0xe8, 0x42, 0x42, 0x4b, 0x08, 0xc2, 0xc8, 0x12, 0x12, 0xf6, 0x42, 0x7d, 0x90, + 0xf7, 0x7d, 0x3e, 0xee, 0x47, 0xf3, 0x77, 0x75, 0xeb, 0x6a, 0xdf, 0x7e, 0xee, 0xea, 0xab, 0x7b, + 0xdc, 0x93, 0xf3, 0xcf, 0xd0, 0xf0, 0xa9, 0x55, 0x53, 0xae, 0x6f, 0x5d, 0x3d, 0xd5, 0x9e, 0xf7, + 0xee, 0x7b, 0x8a, 0xf9, 0xe2, 0xaa, 0x38, 0x70, 0x0e, 0x1c, 0xc3, 0x87, 0x99, 0x2e, 0x2f, 0xb4, + 0xe1, 0xc0, 0x38, 0x70, 0x8e, 0x1c, 0x11, 0x83, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, + 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, + 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d, + 0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7, + 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, + 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, + 0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0x96, 0x81, 0x2f, 0xb4, 0x44, 0x40, + 0x2c, 0x3a, 0xe9, 0x98, 0xa7, 0x55, 0xe1, 0x3a, 0x38, 0x70, 0x8e, 0x1c, 0x42, 0x26, 0xf0, 0xd2, + 0x22, 0x4b, 0x16, 0x32, 0x0c, 0xb8, 0x02, 0xb8, 0xde, 0x38, 0xc9, 0x82, 0xe0, 0x4e, 0x80, 0xbf, + 0xa9, 0x4f, 0xbf, 0x6a, 0x7b, 0x05, 0x88, 0xb1, 0x6c, 0xc8, 0xc3, 0xe0, 0xfb, 0xc0, 0xd1, 0x81, + 0xcd, 0x86, 0x81, 0xe5, 0xc0, 0xe5, 0xab, 0xdf, 0x02, 0xea, 0xb6, 0xc3, 0x0e, 0x1c, 0xa3, 0x87, + 0x44, 0x6e, 0x12, 0x64, 0x29, 0x70, 0x41, 0xf0, 0x7a, 0x60, 0xaf, 0xc2, 0x01, 0x77, 0x80, 0xf3, + 0x55, 0x9b, 0x21, 0xf5, 0xf6, 0x0b, 0x81, 0xba, 0x81, 0xc7, 0x54, 0x5b, 0x77, 0xf5, 0xbf, 0x09, + 0xd9, 0xeb, 0xed, 0xb7, 0x45, 0x80, 0x0b, 0x24, 0x04, 0x2c, 0x5b, 0x66, 0xec, 0x35, 0x28, 0xf1, + 0xd7, 0xf0, 0xba, 0xaa, 0xb6, 0xd5, 0x11, 0x61, 0x23, 0x79, 0x27, 0xf0, 0x76, 0xdb, 0x5e, 0x81, + 0x27, 0x3c, 0xc3, 0xfc, 0x6c, 0x14, 0x1c, 0x3b, 0xc7, 0x0e, 0x10, 0xa0, 0x23, 0x91, 0x67, 0x81, + 0x91, 0x81, 0x9e, 0x75, 0x13, 0xaa, 0x4b, 0x38, 0x17, 0x55, 0xb2, 0x5b, 0x05, 0xd7, 0xa3, 0x9c, + 0x0b, 0xaa, 0x7e, 0x93, 0x8d, 0x31, 0xe0, 0x39, 0x48, 0x2b, 0xf9, 0xc7, 0x9c, 0x02, 0xbc, 0x0b, + 0xb6, 0xdb, 0x62, 0xd1, 0xe3, 0xa7, 0xdb, 0x66, 0x8b, 0x76, 0x03, 0xb4, 0xa5, 0x37, 0xdb, 0x64, + 0x70, 0xe1, 0x06, 0x38, 0x2d, 0xcb, 0xef, 0xd4, 0x01, 0x0c, 0x01, 0x86, 0x8b, 0xf7, 0xab, 0x48, + 0x7b, 0x25, 0x81, 0x43, 0x0d, 0x5f, 0x5e, 0xc2, 0x34, 0x84, 0x80, 0xe6, 0x50, 0x3f, 0x20, 0xfa, + 0x98, 0x19, 0xfa, 0xfd, 0xd7, 0xc4, 0x0c, 0x9c, 0x96, 0x55, 0x92, 0x3c, 0x0b, 0x43, 0x3d, 0xe5, + 0xcc, 0x33, 0x8c, 0x1a, 0x0e, 0x65, 0xab, 0x30, 0x31, 0xb4, 0x4d, 0xb9, 0xd6, 0x98, 0xb6, 0x6e, + 0xf3, 0xef, 0x9f, 0x6a, 0xba, 0xb2, 0xfc, 0xb7, 0xc7, 0xa3, 0xc8, 0x88, 0x55, 0x04, 0x62, 0xdd, + 0xa8, 0xd4, 0xe1, 0xc3, 0xd4, 0x70, 0x71, 0x40, 0xee, 0x7a, 0xd2, 0x82, 0xb0, 0xf6, 0x30, 0x8c, + 0x58, 0x6b, 0x36, 0xb2, 0x55, 0x72, 0x81, 0x4f, 0xfd, 0x4d, 0x88, 0xe5, 0x34, 0xee, 0x69, 0xbc, + 0x63, 0x38, 0xd6, 0xf6, 0x16, 0xf4, 0xd8, 0xd8, 0xb6, 0x57, 0x38, 0x77, 0xa8, 0x50, 0x78, 0x72, + 0x56, 0x2c, 0xb0, 0x1d, 0xb4, 0x88, 0xa7, 0x03, 0xb6, 0x95, 0x1e, 0xa7, 0xe5, 0x97, 0x9f, 0x9a, + 0x33, 0x0f, 0x73, 0x6a, 0xba, 0xdb, 0x9f, 0x02, 0x79, 0x3c, 0x7f, 0xb7, 0x34, 0xd7, 0x06, 0xa3, + 0x39, 0xe3, 0xbf, 0xdb, 0xdd, 0x71, 0x76, 0x94, 0x9f, 0x2e, 0x66, 0xd8, 0xe0, 0xd4, 0xaf, 0x95, + 0x70, 0xf4, 0xab, 0xeb, 0xfe, 0x7d, 0x87, 0x5d, 0xce, 0x03, 0x3b, 0x01, 0x8e, 0x1c, 0xe4, 0x66, + 0xff, 0x5a, 0xa7, 0xc6, 0x6d, 0x0e, 0x8b, 0xe3, 0xdb, 0x53, 0xfd, 0x07, 0x02, 0xe5, 0xfc, 0x70, + 0xbd, 0xc2, 0x07, 0x7e, 0x53, 0xbc, 0xab, 0x55, 0xc4, 0x39, 0xea, 0x6b, 0xa7, 0xb1, 0x89, 0xc0, + 0xf6, 0x8b, 0x1d, 0xfa, 0xa7, 0x70, 0x56, 0xaa, 0xf8, 0x74, 0xb0, 0x95, 0x82, 0x1d, 0x15, 0x3e, + 0x24, 0x2f, 0xc0, 0x0a, 0x73, 0x31, 0xfb, 0xcc, 0x65, 0xff, 0xe4, 0x4f, 0xbb, 0xc2, 0x1a, 0x96, + 0x1a, 0x37, 0xe0, 0x25, 0xcf, 0x80, 0x69, 0x1a, 0x77, 0xfe, 0xdd, 0xcf, 0x78, 0x13, 0xf2, 0x16, + 0x32, 0xc0, 0x46, 0xe3, 0x0e, 0x1d, 0x23, 0x87, 0x22, 0x21, 0x38, 0x72, 0x49, 0x70, 0x7b, 0x69, + 0x46, 0x68, 0xc4, 0xf8, 0x64, 0xe4, 0x94, 0x03, 0xb7, 0x7b, 0xb3, 0xf5, 0x27, 0xa2, 0x6f, 0xe0, + 0xb6, 0x2b, 0x45, 0x77, 0xef, 0x7b, 0xc7, 0xab, 0x83, 0xde, 0x72, 0x3b, 0xdf, 0x3c, 0xb0, 0x15, + 0x3c, 0xb7, 0x09, 0xd1, 0xd8, 0x8a, 0xc0, 0x76, 0x47, 0x01, 0x63, 0x34, 0xd6, 0x4e, 0xc1, 0xb8, + 0x16, 0x97, 0x3a, 0x44, 0x8f, 0x25, 0x37, 0x19, 0xe5, 0x1a, 0xd2, 0xac, 0xb1, 0x87, 0xc2, 0x2d, + 0x21, 0xcc, 0x6f, 0x66, 0xde, 0xfb, 0xb2, 0xbc, 0x2b, 0xb8, 0xbb, 0xf0, 0xa8, 0x97, 0x1e, 0xea, + 0x46, 0xa3, 0x0e, 0x1d, 0xa3, 0x87, 0x3e, 0x36, 0x2f, 0x8d, 0xfb, 0x1a, 0x43, 0xa1, 0x19, 0x5a, + 0x22, 0xdf, 0x4e, 0x89, 0xfd, 0x70, 0xbe, 0xfa, 0xae, 0xf0, 0xcd, 0x1b, 0xeb, 0xda, 0xef, 0x0d, + 0x66, 0xfa, 0x13, 0xa2, 0xb1, 0x14, 0x07, 0x3d, 0x74, 0x1c, 0xa7, 0xc0, 0x37, 0x9b, 0xd2, 0xff, + 0xc0, 0xfc, 0x38, 0x08, 0xcc, 0x0f, 0x6e, 0x37, 0x87, 0xd4, 0x1c, 0x88, 0x1c, 0x03, 0xda, 0x52, + 0x63, 0x3e, 0x96, 0x44, 0x7f, 0x64, 0xe4, 0xea, 0xd8, 0x2c, 0x27, 0x9b, 0x4c, 0x1f, 0x9f, 0x6e, + 0xd8, 0x6b, 0x01, 0xe4, 0x88, 0x83, 0x0d, 0x1c, 0x0e, 0x5c, 0x07, 0x9c, 0xff, 0x46, 0x0a, 0x54, + 0xc2, 0x6c, 0x32, 0x5b, 0x67, 0xf1, 0x46, 0x53, 0x64, 0x44, 0x5e, 0x08, 0xe1, 0xe2, 0x62, 0xdf, + 0xe9, 0x7e, 0x37, 0x5b, 0x08, 0xf0, 0x15, 0xf0, 0x8d, 0x55, 0x9e, 0x84, 0x8e, 0x1c, 0x22, 0x30, + 0x1e, 0x32, 0x48, 0xf8, 0xbb, 0x69, 0xe0, 0x45, 0x43, 0xaa, 0x8d, 0x93, 0xff, 0x46, 0x57, 0x77, + 0x67, 0x8e, 0x03, 0x3a, 0x8e, 0x03, 0xc0, 0x15, 0x9b, 0x7f, 0x37, 0xb2, 0x4f, 0x77, 0x79, 0x9e, + 0x08, 0xc1, 0x1a, 0xe3, 0xee, 0xe0, 0x27, 0x44, 0xd9, 0x29, 0xe5, 0xaf, 0x75, 0x4b, 0x1e, 0x50, + 0x8e, 0x09, 0x98, 0x9e, 0xc2, 0x61, 0xb3, 0x34, 0xc1, 0x23, 0xdd, 0xae, 0xda, 0xca, 0x03, 0x17, + 0x6a, 0x37, 0xaa, 0x91, 0x35, 0xee, 0x34, 0x6a, 0x30, 0x4a, 0x3c, 0xfc, 0xfc, 0xc2, 0x3f, 0xa8, + 0x7e, 0x14, 0xe7, 0x06, 0x07, 0x80, 0x88, 0x85, 0x1b, 0xfc, 0x59, 0xf0, 0x3a, 0xcc, 0x30, 0xde, + 0x88, 0x17, 0xa7, 0xc8, 0xf5, 0x55, 0x8d, 0x5b, 0xb1, 0x3e, 0xcc, 0x88, 0x8b, 0xc2, 0x8c, 0xf8, + 0xf4, 0x6a, 0xab, 0xb9, 0x7a, 0xf0, 0xf5, 0xe0, 0xf2, 0x22, 0x55, 0x5e, 0x6c, 0xdd, 0xae, 0xd1, + 0xff, 0x63, 0x9f, 0xe4, 0xb2, 0xf0, 0x01, 0x88, 0xef, 0x78, 0x56, 0xb8, 0x4f, 0x0e, 0xa0, 0x98, + 0xb5, 0xfa, 0xe8, 0xe8, 0x1b, 0xf7, 0xe6, 0x55, 0xeb, 0x7f, 0x17, 0xb6, 0xfa, 0x37, 0xb5, 0xad, + 0x69, 0xc3, 0x04, 0x2d, 0x22, 0x2d, 0x80, 0x33, 0xa5, 0x09, 0x1b, 0x6d, 0xe7, 0xe1, 0x4f, 0x15, + 0xb2, 0x05, 0x21, 0x9f, 0x47, 0x1d, 0x70, 0x14, 0x67, 0xc0, 0x30, 0x8f, 0xe7, 0xdf, 0xe7, 0x99, + 0x70, 0x1c, 0x44, 0x62, 0x38, 0xe4, 0x6a, 0xe0, 0x3a, 0xec, 0xf0, 0x5f, 0xc1, 0x3a, 0x8b, 0x37, + 0x39, 0xc8, 0xce, 0x06, 0x13, 0x7d, 0x9d, 0x76, 0x89, 0x6f, 0x80, 0xf3, 0x52, 0xdb, 0xeb, 0xb0, + 0x8f, 0xd8, 0x91, 0x10, 0xc0, 0x61, 0xfc, 0x27, 0xae, 0xfb, 0x6c, 0x39, 0x89, 0xf0, 0x00, 0x50, + 0x74, 0xcf, 0xf9, 0xe6, 0xae, 0xd3, 0xef, 0x80, 0x04, 0xdb, 0x65, 0xdc, 0xde, 0xca, 0x5d, 0x73, + 0x7e, 0x05, 0x23, 0xbb, 0x6f, 0x60, 0x70, 0x1b, 0xa2, 0x47, 0xf8, 0x93, 0x39, 0xb0, 0xb6, 0x02, + 0x7e, 0x1f, 0xa2, 0x7e, 0xe6, 0x29, 0xcb, 0x7f, 0xbb, 0xbf, 0xe0, 0x55, 0xe1, 0xb4, 0x3b, 0x66, + 0x05, 0x1e, 0x7e, 0x60, 0x01, 0xd0, 0x53, 0xaf, 0x35, 0xd5, 0x8d, 0x46, 0x18, 0x1e, 0x13, 0xfc, + 0xf7, 0xbe, 0x3d, 0xa1, 0x3e, 0x63, 0xdc, 0xfe, 0xec, 0x1b, 0xce, 0x1c, 0x81, 0xa4, 0x67, 0xcf, + 0x99, 0x71, 0x9b, 0xc5, 0xdb, 0x4a, 0xf1, 0x59, 0x3f, 0x9e, 0xf4, 0x93, 0x02, 0x15, 0x30, 0xeb, + 0x9a, 0x66, 0xe6, 0xb5, 0x00, 0x38, 0x02, 0xb8, 0x5b, 0x18, 0x38, 0xda, 0x4c, 0x7f, 0xb3, 0x0b, + 0x26, 0x86, 0x8d, 0x1d, 0x46, 0x46, 0x37, 0xbf, 0x6a, 0xa9, 0x85, 0x4f, 0x2f, 0xc3, 0xd7, 0xaf, + 0xd7, 0xde, 0xf4, 0x37, 0x2a, 0x12, 0x75, 0x5d, 0xaa, 0xab, 0x26, 0x76, 0x89, 0x3a, 0xf4, 0x8f, + 0x70, 0x1c, 0x19, 0x3a, 0x56, 0xd0, 0x82, 0x47, 0x32, 0x22, 0x38, 0x0e, 0xb4, 0xae, 0xca, 0x7d, + 0x2c, 0xf0, 0xf3, 0x86, 0x58, 0xaf, 0x99, 0xd2, 0x4f, 0x67, 0x02, 0x70, 0x8d, 0xd8, 0x07, 0x7e, + 0x79, 0x47, 0xda, 0x04, 0x81, 0x9e, 0xaf, 0xed, 0x89, 0x1d, 0xa5, 0xc9, 0x8b, 0xda, 0xf3, 0x3b, + 0xb2, 0x9c, 0xf0, 0x38, 0x1e, 0xde, 0x60, 0xd3, 0x81, 0x13, 0xf5, 0x11, 0x87, 0xf5, 0xf0, 0x77, + 0x84, 0xc9, 0x9e, 0x99, 0x79, 0x49, 0x73, 0xd3, 0x3c, 0x5d, 0xbb, 0xb2, 0xce, 0xfc, 0x17, 0x4d, + 0x71, 0x11, 0xd8, 0x35, 0x71, 0x1d, 0x8b, 0x14, 0x5c, 0x56, 0xdf, 0xe5, 0xed, 0x77, 0xc9, 0xa1, + 0xa3, 0x46, 0x7a, 0x2b, 0xdc, 0x0a, 0x2d, 0xbb, 0x59, 0x50, 0x37, 0x78, 0x9f, 0xf0, 0xec, 0x55, + 0xba, 0x7d, 0x7b, 0x1e, 0x7a, 0x6b, 0xfc, 0x0b, 0x6b, 0x6c, 0x5e, 0xc0, 0x5e, 0x17, 0x8f, 0x31, + 0xb1, 0x9a, 0x05, 0x99, 0x76, 0x5d, 0x6d, 0xc0, 0x84, 0x43, 0x67, 0xc3, 0x1d, 0x9b, 0xe6, 0x09, + 0xe1, 0xfb, 0x67, 0xe5, 0x23, 0x02, 0x55, 0xc1, 0xba, 0xaa, 0xa8, 0xde, 0x28, 0xd1, 0xe2, 0x67, + 0x48, 0x1b, 0x9d, 0x9d, 0xfc, 0xce, 0x3c, 0xef, 0x46, 0xe3, 0x73, 0xf7, 0x64, 0x44, 0xbe, 0x14, + 0x29, 0x42, 0x0c, 0xa7, 0x1a, 0xcb, 0xbe, 0x75, 0xfb, 0x10, 0x6a, 0x77, 0xb3, 0xf4, 0x70, 0x19, + 0xa6, 0xc0, 0xbd, 0x9f, 0xc2, 0x9c, 0x7b, 0x93, 0xbf, 0x03, 0xa3, 0x69, 0xbe, 0x73, 0x2a, 0x8e, + 0x0d, 0xfc, 0xb5, 0x30, 0x71, 0xb4, 0x88, 0xc6, 0xe7, 0x2c, 0x4c, 0x8c, 0x8c, 0xf6, 0x73, 0x7e, + 0x5f, 0x43, 0xf2, 0xb8, 0x77, 0xc5, 0x75, 0x55, 0x85, 0x57, 0xdf, 0xc3, 0xaf, 0x5f, 0x1b, 0xbd, + 0xf6, 0x37, 0x0b, 0x91, 0xd1, 0x3e, 0xa8, 0x77, 0xb6, 0xea, 0x27, 0x44, 0x61, 0x09, 0x01, 0xc5, + 0x47, 0x17, 0xc7, 0x82, 0xb6, 0x96, 0x9f, 0xa7, 0x25, 0x92, 0x78, 0x6b, 0xbc, 0x00, 0x49, 0x6a, + 0xbe, 0x9e, 0xc0, 0xee, 0x4a, 0xdd, 0xfc, 0x23, 0x68, 0x1c, 0xcf, 0x04, 0xf3, 0x72, 0xcd, 0xbf, + 0x59, 0xca, 0xde, 0xf8, 0x1b, 0x05, 0x25, 0x1d, 0x7f, 0x0b, 0xf2, 0xa7, 0x37, 0xb3, 0x23, 0x34, + 0x34, 0x9c, 0xec, 0xcc, 0x7a, 0x6f, 0x33, 0xb6, 0x57, 0x4f, 0x45, 0xc1, 0x1c, 0x5e, 0x23, 0x03, + 0x42, 0x22, 0x10, 0xa8, 0xb5, 0xc6, 0xe7, 0x09, 0x5f, 0x62, 0x57, 0xed, 0xda, 0xee, 0x08, 0x12, + 0xf7, 0x3f, 0x61, 0x52, 0x51, 0xe9, 0xec, 0x23, 0xcb, 0x6d, 0x77, 0x29, 0x0a, 0x6e, 0x7e, 0x8c, + 0xc0, 0x73, 0x89, 0x0d, 0x12, 0x4e, 0xad, 0x83, 0xf0, 0x11, 0xf8, 0x59, 0x82, 0x46, 0xc2, 0x36, + 0x2f, 0xcf, 0x48, 0x2d, 0xfc, 0x37, 0x24, 0x5c, 0x76, 0x10, 0xd3, 0x3f, 0xc3, 0x7e, 0x89, 0xde, + 0x6c, 0x57, 0x51, 0xdc, 0x50, 0x9d, 0xba, 0xa6, 0x8e, 0xf5, 0x17, 0x52, 0xfc, 0x0d, 0x89, 0x3b, + 0x14, 0xc8, 0x15, 0x7c, 0x5e, 0xdf, 0x3c, 0x3b, 0xc2, 0x4b, 0x8e, 0x65, 0x71, 0x89, 0xf7, 0x99, + 0x04, 0x8e, 0x03, 0xbc, 0x0a, 0x9f, 0xf1, 0xde, 0x32, 0x43, 0x38, 0x5c, 0xd9, 0x26, 0x77, 0x3b, + 0xc6, 0xcb, 0x70, 0xbe, 0xc5, 0x0e, 0x54, 0x8f, 0x63, 0x75, 0xc3, 0x85, 0x04, 0x2f, 0xa8, 0x4e, + 0xde, 0xc7, 0x37, 0xb7, 0x85, 0xf6, 0x98, 0x3b, 0x37, 0x77, 0xb7, 0x22, 0x96, 0xc6, 0x0e, 0x03, + 0x60, 0x25, 0x28, 0x59, 0x11, 0xc0, 0xff, 0x9d, 0x3a, 0xb5, 0x16, 0x02, 0x36, 0x91, 0x65, 0x7c, + 0x8f, 0x37, 0xc8, 0xee, 0xd1, 0x30, 0x16, 0x70, 0xba, 0xcc, 0x02, 0xbe, 0x82, 0x6b, 0xe5, 0x4f, + 0xb5, 0x13, 0x47, 0x94, 0x08, 0x3b, 0x44, 0x09, 0x4c, 0x2a, 0x94, 0x77, 0x07, 0xd6, 0x74, 0xeb, + 0xf7, 0x83, 0x6a, 0x77, 0xba, 0xe4, 0xab, 0x59, 0x67, 0xe1, 0x91, 0x70, 0x47, 0x17, 0x02, 0x22, + 0x27, 0x65, 0x73, 0x3f, 0x7b, 0x58, 0x84, 0xa2, 0xdd, 0xc7, 0xe5, 0x79, 0xc1, 0x3b, 0xbb, 0x32, + 0xe0, 0x05, 0xa6, 0xeb, 0x75, 0xec, 0x94, 0x16, 0x07, 0x6f, 0x29, 0xed, 0x7d, 0x70, 0x53, 0x82, + 0xc4, 0x54, 0x03, 0x96, 0x27, 0x2e, 0x88, 0x98, 0x31, 0xc0, 0xd9, 0xa7, 0x80, 0xc8, 0xb4, 0x0e, + 0x3b, 0x67, 0xe7, 0x81, 0x1b, 0x8c, 0x32, 0x0d, 0xe8, 0x0d, 0x6d, 0x28, 0x58, 0xd8, 0xeb, 0xff, + 0xc1, 0x1a, 0x14, 0xe4, 0x33, 0x93, 0x94, 0xe6, 0x67, 0xb6, 0x64, 0x74, 0x60, 0x98, 0xa3, 0xb8, + 0x6f, 0x34, 0xf1, 0x8f, 0x4c, 0xe8, 0xf4, 0xa8, 0x8d, 0xa9, 0x2f, 0x67, 0x9b, 0xf0, 0x93, 0x76, + 0xc9, 0x4e, 0x47, 0x57, 0x53, 0x93, 0x6e, 0x5c, 0xae, 0x57, 0x31, 0x07, 0x2d, 0xb5, 0xfb, 0xc3, + 0x27, 0xc7, 0xe4, 0x13, 0xee, 0xf9, 0x7e, 0xa6, 0x05, 0x76, 0x4d, 0x13, 0xa5, 0x7f, 0x2f, 0xaa, + 0xb0, 0x55, 0x36, 0x77, 0xf7, 0xbe, 0x16, 0xa1, 0x77, 0x7b, 0xea, 0x6e, 0x5b, 0x67, 0xf2, 0x70, + 0x41, 0xf7, 0x6d, 0x55, 0xf3, 0xc8, 0x2b, 0x6d, 0xba, 0x0b, 0x54, 0x3c, 0x0c, 0x97, 0xf1, 0x7c, + 0xb3, 0x7c, 0x30, 0xb5, 0x05, 0x0a, 0xd7, 0x65, 0xc0, 0x96, 0x9b, 0x0d, 0x42, 0x97, 0xc8, 0x42, + 0xbc, 0x5a, 0x59, 0x9e, 0xe0, 0x06, 0x3b, 0x0f, 0x9c, 0x73, 0xb8, 0xba, 0x6c, 0xe6, 0x41, 0xf5, + 0xb9, 0xb1, 0xb2, 0x3f, 0x7c, 0xf6, 0x86, 0xc0, 0x58, 0xbd, 0x75, 0x17, 0xe9, 0x22, 0x53, 0x48, + 0x16, 0x0e, 0xf6, 0x05, 0xad, 0xe0, 0xdb, 0xf1, 0xcb, 0x5e, 0x52, 0xdf, 0x0e, 0x44, 0x5e, 0x25, + 0x63, 0x88, 0x30, 0x06, 0xb4, 0xa4, 0xc7, 0x61, 0x45, 0x59, 0x4a, 0x3a, 0x45, 0xbb, 0x8c, 0xe4, + 0x9a, 0x06, 0x43, 0x78, 0x43, 0xe9, 0x38, 0x06, 0xf0, 0xc3, 0xb7, 0xd2, 0x82, 0x57, 0x30, 0xaa, + 0x55, 0xe0, 0x9a, 0x22, 0x4d, 0xe3, 0xd5, 0xbd, 0x3f, 0x44, 0x52, 0xb0, 0x77, 0x55, 0x48, 0x88, + 0x38, 0x06, 0x5f, 0x11, 0x79, 0x0d, 0xf4, 0x43, 0x2b, 0x00, 0x16, 0x92, 0x11, 0xea, 0xc3, 0x91, + 0x25, 0xf0, 0x18, 0xf7, 0x7c, 0xa2, 0xaa, 0x3f, 0x47, 0x5c, 0x70, 0x2b, 0x60, 0x12, 0x01, 0xfa, + 0x01, 0xee, 0xd7, 0x96, 0xf4, 0xb1, 0xb3, 0xe6, 0x8d, 0xfe, 0x0f, 0x2b, 0x4b, 0x6d, 0x3c, 0xb4, + 0xb7, 0xae, 0xdf, 0x00, 0x18, 0xda, 0x84, 0x29, 0x35, 0xf1, 0xf6, 0x53, 0x15, 0xfa, 0xdc, 0x6e, + 0x59, 0x10, 0x54, 0xea, 0x5e, 0xcd, 0x6c, 0x22, 0xe0, 0x39, 0x1f, 0xf2, 0x90, 0xab, 0x0d, 0x87, + 0xad, 0xcb, 0x45, 0x47, 0x6b, 0xbf, 0x20, 0xdb, 0xdb, 0x5e, 0x03, 0xb7, 0x07, 0x18, 0xd2, 0x5e, + 0xab, 0xc0, 0x56, 0xfd, 0xf7, 0x7f, 0x40, 0xcb, 0xc4, 0xa4, 0xb1, 0x61, 0xc4, 0xe0, 0xc0, 0x25, + 0x78, 0x19, 0xf2, 0x21, 0x36, 0xf1, 0xaa, 0x2f, 0xf4, 0x01, 0xc8, 0x8b, 0xc0, 0xab, 0xe0, 0x02, + 0xc0, 0xd7, 0x55, 0x03, 0x1f, 0x32, 0x91, 0x11, 0x00, 0xb7, 0x0c, 0xff, 0xcf, 0x9c, 0x1a, 0x20, + 0x1b, 0x27, 0xbf, 0xf3, 0x77, 0x73, 0x9e, 0x47, 0x86, 0x83, 0xb3, 0x84, 0x98, 0xe2, 0x34, 0x39, + 0x68, 0xc3, 0x13, 0xc3, 0xf3, 0xc0, 0xbf, 0xa2, 0x14, 0xe7, 0xf1, 0xe0, 0x4f, 0x3a, 0x2d, 0xdb, + 0x77, 0x22, 0xef, 0x00, 0x53, 0xc4, 0xb2, 0xf2, 0x16, 0xc1, 0x79, 0x11, 0xaa, 0x8f, 0x32, 0x3e, + 0x81, 0xd0, 0xf2, 0x22, 0xe0, 0x0e, 0xc0, 0xe3, 0xb0, 0x5b, 0x79, 0xd3, 0x16, 0xed, 0x2b, 0x91, + 0xf5, 0x54, 0xa1, 0x27, 0x98, 0xeb, 0x5c, 0x02, 0x5c, 0x09, 0xaf, 0x86, 0x91, 0xd0, 0x36, 0x61, + 0xc8, 0x89, 0x55, 0xbb, 0xd1, 0x35, 0x5e, 0xb4, 0x32, 0xb1, 0xdb, 0xdb, 0xdb, 0x4b, 0x5b, 0x63, + 0xcb, 0x84, 0x26, 0x27, 0x8d, 0x1b, 0x68, 0xfe, 0x5f, 0x4b, 0xb3, 0xb8, 0x67, 0xf7, 0x7d, 0x55, + 0x82, 0xb2, 0x13, 0xbd, 0x49, 0xf0, 0xd6, 0x0e, 0xef, 0x62, 0x5b, 0x67, 0x59, 0xfb, 0x17, 0xdb, + 0xd8, 0x18, 0x4d, 0xcc, 0xbb, 0xfa, 0x61, 0xab, 0xb5, 0xbc, 0xf6, 0x29, 0xaf, 0x10, 0x6d, 0x34, + 0x0a, 0xa7, 0x50, 0x7f, 0xfb, 0xd5, 0xb0, 0x53, 0x59, 0xfb, 0xce, 0x8a, 0x25, 0x37, 0xad, 0x3e, + 0xe5, 0xaa, 0x9b, 0xae, 0x4c, 0x88, 0xbe, 0x04, 0x4f, 0x81, 0x79, 0x8f, 0xa5, 0x3f, 0x6f, 0x15, + 0x31, 0xc4, 0xb8, 0x15, 0xe8, 0x18, 0x91, 0x13, 0x80, 0x07, 0x54, 0xeb, 0x30, 0x35, 0x20, 0xcd, + 0x33, 0x22, 0x07, 0x81, 0xdf, 0x8b, 0x14, 0x19, 0xe7, 0xa6, 0x3b, 0x4b, 0xca, 0x0b, 0x2e, 0xa2, + 0x1d, 0x7b, 0x73, 0xb1, 0x74, 0x6c, 0xa2, 0x28, 0xfa, 0x23, 0x30, 0x46, 0xaf, 0x04, 0x8a, 0x38, + 0x26, 0x4d, 0x7f, 0x00, 0x91, 0x14, 0x86, 0x0f, 0x9d, 0x1d, 0x11, 0x1f, 0x15, 0x39, 0x0f, 0xb8, + 0x55, 0xbb, 0x86, 0xd3, 0x3f, 0x00, 0x91, 0x16, 0xaa, 0xe9, 0x25, 0xfa, 0xfe, 0xdf, 0x00, 0x9f, + 0xb3, 0xaf, 0x1d, 0x78, 0x45, 0xe0, 0xfe, 0xcc, 0x78, 0xb7, 0x7e, 0x9f, 0xe0, 0x9a, 0xd7, 0x7b, + 0xbb, 0xe2, 0x1f, 0x67, 0xd4, 0x9f, 0xb0, 0xc6, 0xe0, 0xcc, 0x61, 0x6f, 0x6f, 0x01, 0x51, 0xe9, + 0xff, 0x88, 0xf2, 0xa8, 0x6b, 0x95, 0xb1, 0xea, 0x78, 0xa7, 0x37, 0x85, 0x34, 0x42, 0xf4, 0x6c, + 0x76, 0x0b, 0x6e, 0x7a, 0x01, 0x5f, 0x8a, 0xcc, 0x56, 0xfe, 0xfa, 0xc7, 0x57, 0xe8, 0x26, 0x44, + 0x95, 0xe3, 0x4c, 0x35, 0x35, 0x8b, 0x7a, 0xaa, 0xf1, 0x5f, 0x32, 0x5a, 0xf2, 0x22, 0xc0, 0x8f, + 0xec, 0x8b, 0xce, 0xff, 0x8e, 0x37, 0x3c, 0x36, 0xbc, 0x47, 0x44, 0x50, 0x26, 0xbe, 0x43, 0x22, + 0xff, 0x7d, 0x4d, 0xf6, 0x38, 0x12, 0xf1, 0xdf, 0xc2, 0x2d, 0x86, 0xb1, 0xa5, 0x2b, 0xb1, 0x3c, + 0x22, 0x27, 0x61, 0x94, 0x3b, 0x14, 0xc7, 0xb5, 0x28, 0x0f, 0x85, 0xdd, 0xe0, 0x16, 0x01, 0xd2, + 0xb9, 0x8e, 0xfb, 0x01, 0x4b, 0x25, 0x7c, 0x4b, 0xf4, 0xb8, 0x44, 0x41, 0xc2, 0x2e, 0xf3, 0xbe, + 0xd1, 0x2d, 0x27, 0x8a, 0x31, 0xf0, 0xe2, 0x28, 0x00, 0x24, 0x9e, 0x3f, 0x30, 0xb0, 0xaf, 0xcc, + 0xf5, 0xb7, 0x52, 0xf3, 0x72, 0x50, 0xa1, 0x70, 0xfe, 0xaa, 0x82, 0xa1, 0x69, 0xbd, 0x6b, 0x78, + 0x6b, 0x7f, 0x17, 0xb6, 0x39, 0x23, 0x1c, 0xf8, 0x9b, 0xf0, 0x27, 0x44, 0x00, 0x19, 0x21, 0x1b, + 0x0b, 0xe8, 0x1f, 0x4b, 0x8f, 0x86, 0xde, 0x15, 0xb9, 0x7a, 0xa9, 0xd9, 0xae, 0x9c, 0xca, 0x5d, + 0xbf, 0xef, 0xe8, 0x1b, 0xcc, 0xb5, 0x6a, 0x30, 0x9e, 0x30, 0xe1, 0xb7, 0x57, 0x9b, 0xcb, 0x7c, + 0x16, 0xe8, 0x31, 0x4e, 0x0e, 0xbc, 0x4f, 0xf3, 0x72, 0xbb, 0xb5, 0x3c, 0x10, 0xaf, 0x54, 0xc2, + 0xe9, 0x11, 0x53, 0xc0, 0xfa, 0x7f, 0xf0, 0x39, 0xb8, 0xae, 0x4c, 0xef, 0x13, 0x6e, 0x19, 0x4e, + 0xe5, 0x96, 0x21, 0xb8, 0x5a, 0x19, 0x9f, 0x6a, 0x33, 0xaf, 0x02, 0x8e, 0x72, 0xbb, 0x86, 0xca, + 0x02, 0xbe, 0xfa, 0x1b, 0x95, 0x95, 0x05, 0x3d, 0xe4, 0x76, 0x5e, 0x22, 0x15, 0xac, 0xc7, 0xaf, + 0x57, 0xfb, 0x72, 0x22, 0xb0, 0x3e, 0x2f, 0x05, 0x08, 0x3c, 0x94, 0xc0, 0x0c, 0xb6, 0x3b, 0x7f, + 0xba, 0xa1, 0x3e, 0x2f, 0xdd, 0xb2, 0x3d, 0xfc, 0xc8, 0xe8, 0xe2, 0x48, 0x88, 0x35, 0xf0, 0xf5, + 0x51, 0xc6, 0x5f, 0x66, 0x02, 0xf1, 0xce, 0xf0, 0xad, 0x31, 0x38, 0x5c, 0x71, 0xa6, 0x0c, 0xe7, + 0xcb, 0x3d, 0x7b, 0xbc, 0x2b, 0x5c, 0x3b, 0xd3, 0x76, 0xcc, 0x7e, 0x8c, 0xad, 0xb1, 0x95, 0x9f, + 0x1a, 0xfb, 0x8a, 0xf7, 0x86, 0x6d, 0x8c, 0x88, 0xe7, 0xc1, 0x8e, 0xf4, 0xcf, 0x68, 0x49, 0x70, + 0xa4, 0xc2, 0xa4, 0x9e, 0xbd, 0xc4, 0xc5, 0xdb, 0x8b, 0x72, 0xf9, 0x17, 0xfb, 0x8f, 0x21, 0xc8, + 0x8c, 0xad, 0xb5, 0x6f, 0x9f, 0x7a, 0x4e, 0x8e, 0xd5, 0xf0, 0xf0, 0xab, 0x9b, 0xf9, 0xd7, 0x0f, + 0x01, 0x22, 0x1d, 0x18, 0x56, 0xfd, 0xba, 0x9b, 0x0a, 0xb9, 0xe7, 0x5f, 0x16, 0xbb, 0x5f, 0x7b, + 0x8a, 0x8b, 0x5c, 0x2e, 0xe8, 0xdf, 0x22, 0x5c, 0x6f, 0xa3, 0x31, 0x67, 0x58, 0x04, 0x63, 0x9e, + 0xfe, 0x3c, 0x16, 0xec, 0xfa, 0xfe, 0x8e, 0xea, 0xdf, 0x2a, 0x9f, 0xa8, 0x1f, 0x67, 0x0f, 0xea, + 0xe0, 0x3b, 0x36, 0x06, 0x83, 0x0d, 0x90, 0x26, 0xfa, 0x85, 0x0f, 0xff, 0xe7, 0x6b, 0xc3, 0x5c, + 0x3d, 0xc9, 0xbc, 0xcf, 0x08, 0x10, 0x0a, 0x03, 0xcd, 0x5e, 0x2f, 0xd3, 0x72, 0x11, 0xad, 0xbe, + 0x57, 0x02, 0xda, 0x1d, 0x27, 0xce, 0xb5, 0x26, 0xb9, 0x31, 0x79, 0xad, 0x6e, 0x9d, 0xf2, 0x35, + 0x8f, 0x0a, 0xce, 0xec, 0xef, 0x71, 0x1c, 0xb6, 0x6f, 0x77, 0x64, 0x8d, 0x1e, 0x44, 0x3c, 0x0b, + 0x17, 0x8a, 0x2a, 0x2a, 0xf4, 0xdf, 0xc6, 0x0b, 0xf0, 0xb4, 0x69, 0xd5, 0x1c, 0xf6, 0x25, 0x5f, + 0x1a, 0xce, 0xb5, 0x91, 0x2a, 0x32, 0x45, 0xb5, 0xf8, 0x06, 0x83, 0x72, 0xeb, 0xef, 0xe0, 0xc2, + 0xcc, 0xb3, 0x87, 0xf5, 0x33, 0xb7, 0x70, 0xcd, 0xb2, 0x69, 0x83, 0xbb, 0x06, 0x25, 0x4d, 0xab, + 0x05, 0x1d, 0x1a, 0x6a, 0x34, 0x5c, 0xfc, 0xd6, 0x15, 0x73, 0xb7, 0x7a, 0x78, 0x33, 0xda, 0x6d, + 0x7c, 0x46, 0x4c, 0xed, 0x06, 0x47, 0x39, 0x6e, 0x08, 0x6a, 0x5f, 0xa6, 0xd0, 0xe9, 0x1a, 0x7d, + 0xdb, 0x54, 0x2c, 0x5a, 0x74, 0xc4, 0x69, 0x79, 0x45, 0xbb, 0x53, 0xe0, 0x25, 0x09, 0xff, 0x00, + 0xea, 0x1c, 0x01, 0x94, 0x81, 0x2b, 0x98, 0x5f, 0x37, 0xb2, 0x4f, 0x77, 0xc7, 0x9e, 0x24, 0x1b, + 0x58, 0x39, 0xd3, 0x0d, 0xe8, 0x21, 0x7a, 0xc0, 0x63, 0xc4, 0xb0, 0xcf, 0x80, 0x51, 0x32, 0x23, + 0xb5, 0x1f, 0xb8, 0xc0, 0x28, 0xd1, 0x05, 0xd6, 0x9e, 0x18, 0x3e, 0x08, 0x1b, 0x2c, 0xf7, 0x81, + 0xd3, 0xe2, 0x04, 0xbd, 0x6f, 0x38, 0x0b, 0x64, 0x9c, 0xcf, 0x9c, 0x38, 0x4e, 0xaf, 0xdf, 0x6b, + 0x9d, 0x6f, 0x78, 0x2f, 0x02, 0xc2, 0x1c, 0x6f, 0xc2, 0xd5, 0x47, 0xad, 0xf0, 0x75, 0xc9, 0xc2, + 0xb9, 0xc3, 0x42, 0x6c, 0x38, 0x6b, 0x78, 0x1e, 0x10, 0x18, 0xf7, 0x90, 0x90, 0x3b, 0xde, 0x9c, + 0x4c, 0x81, 0x4c, 0x58, 0x4a, 0x64, 0xc6, 0x5b, 0xf3, 0xb8, 0x59, 0x8d, 0xff, 0x3f, 0x95, 0x54, + 0xad, 0xc3, 0x8b, 0xca, 0xbc, 0xc6, 0x1b, 0xdb, 0x75, 0x54, 0x31, 0x63, 0xa3, 0xa7, 0x02, 0x3c, + 0x75, 0x3d, 0xd6, 0x58, 0xf3, 0xb8, 0xb9, 0x8d, 0xb1, 0x4f, 0x4f, 0xc9, 0x04, 0x31, 0x00, 0x24, + 0x19, 0x2f, 0x83, 0x5f, 0x60, 0x04, 0xf4, 0x74, 0x8d, 0xfb, 0x35, 0x2a, 0x3e, 0x0e, 0x6f, 0x81, + 0xb3, 0xda, 0x7b, 0x16, 0xa3, 0x31, 0x6f, 0xdf, 0x6a, 0xaa, 0xdc, 0x7f, 0xf5, 0xb1, 0xd5, 0x60, + 0xdb, 0x01, 0x40, 0x51, 0x5d, 0x4a, 0x61, 0x80, 0x3e, 0x2c, 0x86, 0x01, 0x87, 0x93, 0x17, 0x99, + 0x58, 0x60, 0x99, 0xda, 0x77, 0x67, 0x14, 0x70, 0xc1, 0x98, 0xb8, 0x4b, 0x73, 0x2a, 0xc4, 0xc5, + 0xfe, 0x36, 0xc2, 0x74, 0x66, 0x50, 0xbe, 0x9a, 0xcb, 0x18, 0xd2, 0x1c, 0x7c, 0x2a, 0xe8, 0xff, + 0xfc, 0x21, 0xd8, 0xe2, 0x55, 0xa1, 0x37, 0xeb, 0x58, 0xdb, 0xe4, 0x5e, 0x9f, 0xa8, 0xba, 0xe7, + 0xcf, 0x82, 0x13, 0x72, 0x9f, 0xae, 0x96, 0x0f, 0x1d, 0x97, 0xb6, 0x69, 0x8b, 0x4d, 0xb7, 0x79, + 0xd7, 0x4b, 0x96, 0x76, 0xe7, 0x7f, 0x86, 0xd9, 0x03, 0xaf, 0xab, 0x6a, 0x10, 0x33, 0x01, 0x27, + 0x75, 0x78, 0x11, 0x42, 0x67, 0x84, 0xf3, 0x3b, 0xb4, 0x3c, 0xd0, 0x33, 0x6f, 0xae, 0x7e, 0x06, + 0xc1, 0x50, 0xd0, 0xde, 0xed, 0x7a, 0xd7, 0x3d, 0xe0, 0x55, 0x0d, 0x77, 0x1c, 0x94, 0x86, 0xac, + 0x07, 0x35, 0x31, 0xdc, 0x38, 0x4c, 0x96, 0x7c, 0x01, 0x79, 0x95, 0x86, 0x83, 0x0b, 0x0a, 0x61, + 0x7d, 0x55, 0xa8, 0xd5, 0x28, 0xd1, 0x10, 0x81, 0x4a, 0x55, 0x0c, 0x02, 0xf1, 0x13, 0x5f, 0x85, + 0x3f, 0x5f, 0x85, 0xaa, 0x1d, 0x6f, 0x36, 0x69, 0x9f, 0xf4, 0x17, 0x36, 0x6d, 0x8d, 0xbe, 0xe1, + 0x86, 0xe3, 0x96, 0xc6, 0x9c, 0x5c, 0x26, 0xdc, 0x69, 0x1c, 0x5d, 0x5a, 0xd1, 0xc1, 0xc2, 0x10, + 0xb2, 0xcf, 0x75, 0x95, 0xd1, 0xd9, 0x54, 0x65, 0x8e, 0xb1, 0x58, 0xa5, 0xee, 0xdd, 0xa5, 0xb5, + 0xa3, 0xed, 0x32, 0x3a, 0x32, 0x4c, 0xb4, 0x5c, 0x4a, 0xa5, 0x4f, 0x89, 0xd5, 0xeb, 0x62, 0xce, + 0xcf, 0x96, 0x46, 0x0d, 0xe5, 0xe8, 0x7c, 0x72, 0xfd, 0x21, 0x49, 0x06, 0xde, 0x0e, 0x45, 0x5c, + 0xf0, 0x72, 0xf8, 0x23, 0x3d, 0xa6, 0x41, 0x9b, 0xb0, 0x72, 0xc6, 0x1a, 0xa5, 0x5a, 0xe1, 0x62, + 0xa3, 0x83, 0xdb, 0x4a, 0x9f, 0x47, 0x25, 0x96, 0xf8, 0x63, 0x59, 0x19, 0x38, 0xb5, 0x5f, 0x4f, + 0xea, 0x4f, 0xe1, 0x05, 0xa8, 0x53, 0x54, 0x42, 0x19, 0x79, 0xd1, 0xc8, 0x19, 0x1d, 0x99, 0xee, + 0xc4, 0x31, 0x16, 0xb8, 0x33, 0x6c, 0xe6, 0xf0, 0x2d, 0x51, 0x74, 0x7c, 0x74, 0x62, 0xe7, 0xae, + 0x0b, 0x4c, 0x71, 0x4b, 0x8d, 0x1c, 0x34, 0x21, 0xc6, 0xef, 0x16, 0x39, 0xcb, 0x1c, 0x41, 0x63, + 0x05, 0x9b, 0xaf, 0x2a, 0x3e, 0x61, 0x5f, 0x05, 0xbb, 0x75, 0x4b, 0x94, 0x96, 0x7c, 0x19, 0xdb, + 0x4e, 0xc1, 0xf9, 0x9d, 0x67, 0x50, 0xc7, 0x20, 0xd3, 0x00, 0x8d, 0x7b, 0x1c, 0x90, 0xb9, 0x7c, + 0xc0, 0xa7, 0xb7, 0x2f, 0x22, 0x07, 0x7b, 0xbf, 0xdb, 0x80, 0xd8, 0x2a, 0xa1, 0x6b, 0xc9, 0x4e, + 0xd5, 0xc1, 0x24, 0x06, 0xab, 0x07, 0x72, 0x61, 0x90, 0x58, 0xfc, 0xc2, 0xf3, 0x6f, 0x30, 0x09, + 0xa4, 0xa4, 0x54, 0x80, 0xc6, 0xe6, 0x3a, 0x23, 0x6b, 0x18, 0x12, 0xba, 0x13, 0x7f, 0xdd, 0x71, + 0xf6, 0xd4, 0xb6, 0x48, 0x9b, 0xa3, 0xdd, 0xff, 0xf0, 0x7f, 0x46, 0x8f, 0x67, 0x87, 0xd9, 0x85, + 0xcd, 0x43, 0xed, 0xfe, 0xe6, 0xf6, 0x25, 0x95, 0x75, 0xc1, 0x4b, 0xd9, 0x75, 0x54, 0x85, 0x77, + 0xad, 0xc3, 0xff, 0xce, 0xbc, 0x42, 0x8a, 0xb8, 0x0c, 0x88, 0xeb, 0xc1, 0x00, 0x0c, 0xdc, 0x70, + 0x8c, 0x90, 0x77, 0xf1, 0x4f, 0x7c, 0x3e, 0xf4, 0x54, 0x2d, 0xae, 0xd5, 0xbe, 0x01, 0x7e, 0xf6, + 0xd5, 0x7c, 0x6a, 0xe7, 0x1c, 0x95, 0x2d, 0x0c, 0xbb, 0xdb, 0x55, 0x5d, 0x56, 0x77, 0xfc, 0x38, + 0xf8, 0x7d, 0xb6, 0x8a, 0x62, 0xd4, 0x26, 0xef, 0x92, 0x03, 0xd5, 0x83, 0xe7, 0xb0, 0x6d, 0x80, + 0xff, 0xcc, 0xec, 0xa6, 0xc3, 0x80, 0x2a, 0x21, 0x02, 0x5b, 0xa1, 0x0c, 0xfe, 0xc2, 0x26, 0x65, + 0x0a, 0xb5, 0x1d, 0x6f, 0x70, 0x7d, 0xfe, 0x8d, 0xb4, 0xc6, 0xff, 0x08, 0xca, 0x1a, 0xb5, 0x4a, + 0x52, 0x31, 0xd4, 0x71, 0xc6, 0xcf, 0xe5, 0x3e, 0xdf, 0x15, 0x7c, 0xb3, 0xf0, 0xfa, 0x59, 0x2c, + 0x7d, 0xc3, 0x27, 0xc7, 0x46, 0xf6, 0x65, 0xd6, 0x7a, 0x0f, 0xf7, 0xfe, 0xc3, 0x6c, 0xeb, 0x9d, + 0xa2, 0x2a, 0x73, 0x1d, 0x2d, 0xf0, 0xb1, 0xb1, 0xff, 0x31, 0xf0, 0x9b, 0x35, 0x3e, 0x02, 0x44, + 0x53, 0x70, 0x72, 0x54, 0x8f, 0xb0, 0x92, 0x0a, 0x45, 0x83, 0xb2, 0xea, 0x35, 0xb7, 0x72, 0x5e, + 0x9b, 0xf0, 0x4e, 0x89, 0x10, 0x92, 0x0e, 0x93, 0xdc, 0xf8, 0x87, 0xd2, 0x0b, 0x6c, 0x3f, 0x4c, + 0x82, 0xd5, 0x52, 0x02, 0x03, 0x30, 0x0d, 0x3a, 0x86, 0x01, 0x88, 0x40, 0xa5, 0x2a, 0x92, 0x04, + 0x7f, 0x32, 0xa1, 0x33, 0x78, 0xd5, 0xd6, 0x03, 0xbc, 0x8c, 0xec, 0x6b, 0x75, 0xbe, 0x43, 0x99, + 0x18, 0x32, 0xc2, 0xd7, 0x34, 0x75, 0xb0, 0x86, 0x45, 0x48, 0xf9, 0x4a, 0xd5, 0x13, 0x4b, 0x1c, + 0xf1, 0x39, 0x4c, 0x69, 0x30, 0x8b, 0xe1, 0x33, 0x62, 0xdc, 0x62, 0x52, 0x9c, 0x0a, 0xc2, 0x44, + 0x60, 0xcf, 0x9d, 0xa9, 0xe4, 0x19, 0xb9, 0x3c, 0xba, 0x8f, 0x00, 0x25, 0xd9, 0x6f, 0x47, 0xd8, + 0xee, 0xb0, 0x3f, 0x06, 0xd5, 0x56, 0x71, 0xf5, 0x8a, 0xc7, 0x05, 0xc8, 0xec, 0xc0, 0xec, 0x7a, + 0x16, 0xdd, 0x1c, 0x95, 0x99, 0xfc, 0xae, 0xaa, 0x74, 0x4e, 0x84, 0x92, 0x79, 0x18, 0x44, 0x82, + 0x24, 0x64, 0x9a, 0xf0, 0xf6, 0xd2, 0xb3, 0xe1, 0xb4, 0xb3, 0x35, 0x0f, 0x09, 0xeb, 0xad, 0x36, + 0xb0, 0x2a, 0x73, 0x6b, 0xaf, 0xe0, 0x78, 0xf5, 0x02, 0x0a, 0xcd, 0x0c, 0x62, 0xf0, 0xb9, 0x94, + 0x42, 0x6c, 0xb5, 0xb2, 0x07, 0x6f, 0x87, 0x0a, 0x0a, 0xb4, 0x8a, 0x63, 0xb9, 0x21, 0x86, 0xd3, + 0x0f, 0x67, 0xf9, 0x8a, 0x03, 0xef, 0xf0, 0x81, 0x6f, 0xfc, 0x25, 0x93, 0x1d, 0x60, 0x1c, 0x03, + 0xda, 0x52, 0x63, 0x3e, 0xd6, 0x44, 0x29, 0x78, 0x2a, 0xac, 0x66, 0xd7, 0x9d, 0xbd, 0x41, 0xa9, + 0xb9, 0xef, 0x09, 0x2e, 0x3d, 0xc0, 0xe5, 0x79, 0x9b, 0x3b, 0x12, 0x22, 0x46, 0x80, 0x07, 0x25, + 0x8d, 0x6f, 0x0e, 0x4a, 0xb4, 0x36, 0x64, 0xec, 0xa6, 0x1c, 0x17, 0x62, 0xe2, 0x67, 0xee, 0x73, + 0xc2, 0x36, 0xcb, 0xcf, 0x5e, 0x53, 0x54, 0x7e, 0x94, 0x24, 0x95, 0x80, 0x1d, 0x5e, 0xe9, 0xde, + 0x99, 0x1d, 0xd9, 0xc4, 0xf5, 0x7f, 0x6c, 0xf0, 0x9e, 0x67, 0x6d, 0xef, 0x67, 0xea, 0x4f, 0x45, + 0x97, 0x81, 0x0a, 0x7b, 0x30, 0x28, 0x4d, 0xa5, 0xd6, 0x98, 0xe1, 0x9e, 0xa3, 0x6d, 0xf8, 0x5b, + 0x00, 0xc0, 0xa8, 0x55, 0x4d, 0x1a, 0x4d, 0x69, 0xfc, 0xe4, 0x79, 0x3d, 0x3f, 0xc6, 0xa7, 0xb9, + 0xb1, 0xea, 0x1e, 0x37, 0xc1, 0xd1, 0x86, 0xa5, 0xef, 0xaf, 0xcf, 0xcd, 0xf1, 0x77, 0xa7, 0x6c, + 0x1a, 0x9d, 0xc7, 0xd0, 0xc9, 0x0e, 0x77, 0x30, 0xd9, 0x96, 0xaf, 0xea, 0xde, 0x3f, 0xcf, 0xd2, + 0xab, 0x95, 0x25, 0x2a, 0xd6, 0xa2, 0xa7, 0x46, 0xc6, 0x2f, 0x15, 0x99, 0x04, 0x2f, 0x92, 0x6d, + 0x7a, 0xbc, 0x55, 0xa1, 0xeb, 0x1f, 0x8c, 0xab, 0x82, 0x78, 0x8f, 0xcb, 0x3b, 0xec, 0x02, 0xbd, + 0xbf, 0x30, 0xda, 0x18, 0x8f, 0x2d, 0x27, 0xc4, 0x0a, 0xb6, 0x1b, 0x47, 0x12, 0xae, 0x73, 0x16, + 0x56, 0x9f, 0xea, 0xa9, 0x4a, 0xc6, 0x37, 0xc7, 0x02, 0x42, 0xf6, 0xf0, 0xfd, 0x4d, 0x0c, 0xec, + 0xc6, 0x4e, 0x5a, 0xfe, 0x5f, 0x55, 0x60, 0xac, 0x4c, 0xef, 0x11, 0x7c, 0x5a, 0xd7, 0x07, 0x25, + 0x07, 0xb7, 0xfd, 0xed, 0x51, 0xb6, 0x83, 0x92, 0x30, 0xd5, 0x21, 0xe5, 0xaf, 0xec, 0x6d, 0xff, + 0x04, 0xb2, 0xb1, 0xac, 0x62, 0x2d, 0xcd, 0x7c, 0xde, 0xed, 0xb2, 0xbc, 0x42, 0xb8, 0xff, 0x61, + 0x59, 0xd7, 0xf7, 0x5a, 0x5d, 0xda, 0x2f, 0x80, 0xed, 0xae, 0xa6, 0xe1, 0x91, 0x8d, 0x09, 0x64, + 0xf7, 0x56, 0x8d, 0x7a, 0xdd, 0xc8, 0xfb, 0x5b, 0x7f, 0xb9, 0x8e, 0x47, 0xa7, 0x3b, 0x30, 0x77, + 0x42, 0xc2, 0x7c, 0x4c, 0x5e, 0x4f, 0xd4, 0xd9, 0x5d, 0xef, 0xbf, 0x4c, 0x7e, 0x12, 0x23, 0x91, + 0x6a, 0x9c, 0xa1, 0xb8, 0x2a, 0xef, 0x73, 0x28, 0xe7, 0x77, 0xb7, 0xe8, 0x8f, 0x14, 0x9d, 0x31, + 0x04, 0xb8, 0x97, 0xf0, 0xb9, 0xdd, 0xef, 0x01, 0x3d, 0x6b, 0x1e, 0xcc, 0x35, 0x9a, 0xac, 0xfe, + 0xe9, 0x2c, 0xb3, 0xca, 0xf8, 0x27, 0x56, 0xd0, 0xb9, 0x53, 0x74, 0x09, 0x4d, 0xae, 0x5c, 0x8b, + 0xbc, 0xc4, 0x4b, 0xdb, 0x75, 0x55, 0x51, 0x57, 0x68, 0x51, 0x69, 0x74, 0x8d, 0xa7, 0xcf, 0x24, + 0x5d, 0xc5, 0xf6, 0x79, 0xaf, 0xc6, 0xae, 0x45, 0x5d, 0x44, 0x80, 0x12, 0x0a, 0x7d, 0x27, 0x37, + 0x1c, 0x93, 0x8c, 0x0c, 0x1b, 0x7e, 0x39, 0x24, 0x4d, 0x58, 0xa1, 0xe4, 0x98, 0x61, 0x10, 0xf2, + 0xfa, 0xe7, 0x52, 0x37, 0x80, 0xa8, 0x60, 0x35, 0x7c, 0x8a, 0x49, 0xa3, 0x44, 0x23, 0x20, 0xb5, + 0x18, 0x9a, 0xe6, 0x51, 0x16, 0x75, 0xdf, 0x46, 0x57, 0x00, 0xea, 0xaf, 0x86, 0xe1, 0xa6, 0x68, + 0x84, 0x62, 0xb7, 0x7b, 0xf3, 0xf5, 0x3b, 0x3f, 0x14, 0xed, 0x5f, 0xed, 0x13, 0xd1, 0x94, 0x9f, + 0xc7, 0x16, 0x0b, 0x18, 0xa5, 0x1d, 0xf7, 0x27, 0xad, 0xe6, 0x5f, 0x39, 0x2d, 0x48, 0xd9, 0x24, + 0x9a, 0x50, 0xfe, 0x70, 0x6a, 0xd7, 0xbb, 0x75, 0x77, 0x98, 0x3e, 0x3b, 0x6c, 0xfc, 0xa3, 0xa1, + 0xe4, 0x9d, 0x35, 0x3e, 0x02, 0x42, 0xc7, 0xf0, 0xfa, 0x5b, 0x10, 0xd9, 0x1c, 0x94, 0x44, 0x7c, + 0x9f, 0x55, 0x60, 0xa8, 0x26, 0x6f, 0x64, 0x9e, 0xcd, 0x65, 0x62, 0xf6, 0x07, 0x24, 0x35, 0xe7, + 0x72, 0x52, 0x1a, 0xb0, 0x6f, 0x0d, 0x8b, 0xa5, 0xbf, 0x6b, 0x94, 0xe6, 0x27, 0xb6, 0xcf, 0x4d, + 0x72, 0x30, 0x59, 0xbc, 0xd4, 0x57, 0x4b, 0xb5, 0xbf, 0x59, 0x71, 0x2c, 0x7c, 0x8a, 0x69, 0xa3, + 0x79, 0x86, 0x32, 0x99, 0x6b, 0xa6, 0x83, 0x4f, 0x48, 0xd7, 0xbe, 0xdc, 0xd2, 0x29, 0x04, 0x73, + 0x72, 0xaf, 0x37, 0x4c, 0x09, 0x30, 0x33, 0xdf, 0x60, 0xb6, 0x9e, 0x6f, 0x8f, 0x77, 0x1f, 0x33, + 0x5e, 0x8a, 0x75, 0x1e, 0x6b, 0xc0, 0xed, 0x81, 0xcf, 0xc5, 0xf0, 0xc3, 0xf0, 0x95, 0xd3, 0x3b, + 0x47, 0x42, 0xe6, 0x29, 0xb4, 0xef, 0x6f, 0x12, 0x63, 0x3d, 0x98, 0xa3, 0x4f, 0x81, 0xc3, 0xfc, + 0xb3, 0xf4, 0x35, 0xb7, 0x2c, 0xa7, 0x0e, 0xeb, 0xe3, 0xdc, 0xc0, 0xdd, 0x75, 0x2b, 0x76, 0x9e, + 0xbe, 0x0a, 0x0d, 0x82, 0x54, 0xcd, 0xab, 0x01, 0x5b, 0x58, 0x6a, 0xd8, 0xbb, 0x75, 0x4e, 0x2a, + 0xd0, 0x2f, 0x58, 0xce, 0x8b, 0x59, 0x2c, 0x9c, 0xab, 0x01, 0xc9, 0x6e, 0x1b, 0xc1, 0x12, 0xa2, + 0xb6, 0x80, 0x07, 0x25, 0x8c, 0xad, 0xc1, 0x7e, 0x08, 0x4c, 0x54, 0xfa, 0x8d, 0x7c, 0x95, 0x81, + 0x47, 0xe5, 0xe6, 0x4f, 0x04, 0xd0, 0xf2, 0x29, 0xb0, 0x86, 0xa2, 0x1a, 0xca, 0x82, 0xd0, 0x19, + 0xda, 0xd3, 0x1c, 0x33, 0xb8, 0x61, 0x93, 0xf2, 0x1f, 0x02, 0xd9, 0x5d, 0xc8, 0x43, 0xdd, 0xd5, + 0x7c, 0x07, 0x34, 0xac, 0x54, 0x3f, 0xcb, 0x2c, 0x5e, 0x59, 0xb7, 0x43, 0x73, 0xa8, 0x23, 0xf0, + 0x7a, 0xd3, 0xde, 0x58, 0xb1, 0x1d, 0x89, 0xf3, 0x26, 0x92, 0x88, 0xb4, 0x10, 0x7c, 0x19, 0x5f, + 0x94, 0xbb, 0xe5, 0x12, 0xae, 0xde, 0xcb, 0xaa, 0x71, 0x1a, 0xe2, 0xee, 0xe0, 0x9c, 0x8a, 0x36, + 0xdc, 0xef, 0x16, 0x21, 0x04, 0xe6, 0xc9, 0xc3, 0xc0, 0x52, 0x55, 0xf7, 0x53, 0xf5, 0x9c, 0xf5, + 0x44, 0x53, 0x8f, 0x7e, 0x22, 0xa7, 0xa4, 0x06, 0x80, 0x92, 0x17, 0xb5, 0x54, 0x58, 0x50, 0xe1, + 0x4e, 0xfb, 0x10, 0x6f, 0xb0, 0x15, 0x64, 0x86, 0xce, 0xbc, 0xe5, 0x80, 0xd9, 0x6b, 0xc4, 0x0d, + 0x8d, 0xce, 0x42, 0x36, 0xf0, 0x02, 0xed, 0xbe, 0xe1, 0xfd, 0xdd, 0xce, 0x55, 0x22, 0x7d, 0xfd, + 0x7b, 0x05, 0xf0, 0x53, 0x22, 0x49, 0x48, 0x36, 0xf0, 0x72, 0x1a, 0x79, 0x39, 0x2c, 0x0d, 0x58, + 0x5e, 0x13, 0x7e, 0xc2, 0xf9, 0xf8, 0x81, 0x89, 0x68, 0x7b, 0x1a, 0xc5, 0xef, 0xa1, 0xce, 0x01, + 0xb9, 0x20, 0x67, 0x96, 0x58, 0x01, 0x7c, 0x07, 0x55, 0x5b, 0x55, 0x77, 0xa6, 0x38, 0x92, 0xfc, + 0x59, 0xb6, 0x60, 0x12, 0x5f, 0x98, 0x01, 0xc1, 0x98, 0xdb, 0x7d, 0xff, 0x6f, 0x7a, 0xc5, 0x9e, + 0xa4, 0xd1, 0x1e, 0xa2, 0x3a, 0xe0, 0x27, 0xd9, 0x18, 0x6d, 0x7f, 0xa8, 0xc1, 0x39, 0x3a, 0x52, + 0x4c, 0x72, 0x7a, 0x08, 0xef, 0xb9, 0xa8, 0x73, 0x35, 0x31, 0x7c, 0xfb, 0x30, 0x2a, 0x98, 0x3f, + 0xb2, 0xc0, 0x51, 0xa5, 0xb7, 0xce, 0xf1, 0xc8, 0x54, 0xf8, 0xec, 0x69, 0x28, 0x74, 0x53, 0x22, + 0x75, 0x09, 0xeb, 0x85, 0x24, 0x44, 0x97, 0x83, 0x09, 0xb8, 0x49, 0x78, 0x53, 0x34, 0xcc, 0xdb, + 0x22, 0x76, 0xf6, 0xd6, 0xc2, 0x15, 0xc7, 0x61, 0xaa, 0x27, 0xf7, 0x14, 0x1a, 0xb7, 0xad, 0xf6, + 0xc0, 0x20, 0xaa, 0xb0, 0x33, 0xa8, 0x64, 0x7e, 0xe7, 0x69, 0x2d, 0x6b, 0xf8, 0x64, 0xee, 0xaf, + 0xb0, 0x2d, 0x2f, 0xae, 0xba, 0xb2, 0x52, 0xa4, 0xba, 0x4f, 0xc9, 0x7e, 0x68, 0xc1, 0x49, 0xd9, + 0x92, 0xdb, 0x5f, 0x83, 0xb9, 0xb3, 0xc6, 0x7a, 0xeb, 0x44, 0x3d, 0x63, 0x6b, 0x01, 0x00, 0xf9, + 0x58, 0x09, 0x72, 0x22, 0x70, 0x15, 0x30, 0x1d, 0x4e, 0xb2, 0x97, 0x53, 0xc8, 0x89, 0x85, 0x5f, + 0xd0, 0x1b, 0x00, 0x88, 0xf0, 0xc3, 0x86, 0x2c, 0x21, 0x07, 0xe9, 0x2c, 0x24, 0x8c, 0x94, 0x90, + 0x31, 0xb1, 0x7b, 0xff, 0xf6, 0xdf, 0xf9, 0xd8, 0x4b, 0xdc, 0x7d, 0xaa, 0x18, 0xe7, 0x37, 0xd3, + 0x48, 0xa5, 0x04, 0x28, 0xc0, 0x5d, 0xf9, 0x0b, 0xb0, 0x96, 0x97, 0x3a, 0x7c, 0xf7, 0x21, 0x57, + 0xac, 0x9c, 0x98, 0x34, 0xb9, 0xfa, 0xb0, 0x49, 0x79, 0x5e, 0x86, 0xad, 0x29, 0x25, 0x31, 0x03, + 0xdf, 0x1b, 0xd9, 0xbb, 0xcb, 0x4c, 0x8a, 0x8f, 0xbd, 0x58, 0x31, 0xc7, 0x0c, 0x02, 0x0e, 0xa5, + 0xd5, 0x8e, 0x23, 0xce, 0x0e, 0x24, 0xd7, 0xd3, 0x01, 0x25, 0xf0, 0xf8, 0x1c, 0xae, 0x86, 0x4f, + 0xad, 0xb2, 0xf4, 0xf1, 0xe3, 0xcc, 0x8d, 0xec, 0xae, 0x01, 0xc7, 0xa8, 0x8c, 0x5e, 0xe0, 0xe4, + 0x0c, 0x5a, 0x0e, 0x48, 0xa3, 0x56, 0x2d, 0xa1, 0x31, 0x62, 0x56, 0x1c, 0x48, 0x7e, 0x4d, 0x46, + 0x26, 0x10, 0x65, 0x1d, 0x45, 0x38, 0x1f, 0xbe, 0x7e, 0x5e, 0x70, 0x26, 0x5f, 0x13, 0xa7, 0xf1, + 0x8b, 0xc8, 0x0c, 0xc8, 0x98, 0xbc, 0xbd, 0x7e, 0x17, 0xce, 0xad, 0x3c, 0x27, 0xd9, 0x35, 0x8a, + 0xfd, 0x47, 0x83, 0x34, 0x43, 0x00, 0x1b, 0x19, 0xbf, 0xf3, 0xed, 0xf7, 0xae, 0x48, 0x65, 0xcc, + 0x34, 0x3a, 0x62, 0x5a, 0x30, 0x45, 0xb5, 0x84, 0x55, 0x56, 0x21, 0x7b, 0xb9, 0xad, 0xc6, 0xbc, + 0x16, 0x8e, 0xb0, 0x63, 0x94, 0x5a, 0xb7, 0xbf, 0x4f, 0x00, 0x23, 0x07, 0x2f, 0x1f, 0x40, 0x37, + 0xc4, 0xf0, 0x88, 0x7a, 0xe6, 0x37, 0x89, 0xfd, 0xbb, 0xfd, 0x48, 0x88, 0x70, 0x3b, 0x69, 0x5a, + 0x46, 0xdb, 0xbb, 0x75, 0x06, 0xc7, 0xff, 0x39, 0xd2, 0x9b, 0x79, 0x3c, 0x0e, 0xa5, 0xdd, 0x64, + 0x80, 0x12, 0x25, 0x09, 0x3f, 0x07, 0x0c, 0x0e, 0xc7, 0xaf, 0x2d, 0x9e, 0xc1, 0xc9, 0x34, 0x6a, + 0xf9, 0xba, 0x89, 0x99, 0x91, 0x2f, 0xc6, 0xc2, 0x77, 0xe8, 0x92, 0xd5, 0x05, 0x01, 0x3e, 0x18, + 0x2f, 0x8b, 0xd1, 0x4c, 0x46, 0x00, 0x13, 0xe1, 0x28, 0x99, 0x34, 0xd2, 0x9f, 0xf5, 0xb7, 0x27, + 0xf1, 0x89, 0xe6, 0x91, 0x9e, 0x8a, 0x78, 0x42, 0x64, 0xde, 0xd8, 0x50, 0xc0, 0x70, 0xfb, 0x9b, + 0xad, 0x95, 0x11, 0xc1, 0x3f, 0xa7, 0x1c, 0xfc, 0x28, 0xe8, 0xad, 0x25, 0x5b, 0x13, 0xb5, 0x0b, + 0xb8, 0x48, 0xa4, 0x25, 0xec, 0xfc, 0xc9, 0x0d, 0x29, 0xc0, 0x47, 0x86, 0xcf, 0xf4, 0xe6, 0x74, + 0x55, 0x09, 0x6d, 0x3e, 0xef, 0xc4, 0xdb, 0x49, 0xe7, 0x01, 0xf9, 0x81, 0x0e, 0x15, 0x0d, 0x3d, + 0xd7, 0x28, 0x4c, 0x34, 0x39, 0x39, 0x98, 0xec, 0x0e, 0xd2, 0x02, 0x5b, 0x8f, 0x0c, 0x8e, 0xed, + 0xa7, 0xdc, 0x36, 0x48, 0x71, 0x72, 0xef, 0x7a, 0xfc, 0x9f, 0x92, 0xfb, 0x00, 0x4a, 0xea, 0x2e, + 0xf3, 0xcc, 0xe0, 0x87, 0x89, 0x2a, 0xc0, 0x2d, 0xe3, 0xf7, 0x2d, 0x8e, 0xc2, 0xd6, 0xe7, 0xfe, + 0xec, 0x7a, 0x48, 0xc5, 0x6e, 0x0e, 0xbe, 0x07, 0xe8, 0x97, 0x0d, 0x24, 0xd1, 0xa0, 0xa2, 0x9e, + 0x05, 0x19, 0x5e, 0x18, 0xfa, 0x5f, 0xd6, 0xd0, 0x7c, 0x21, 0x8f, 0xf9, 0xc9, 0xc3, 0x5c, 0x24, + 0xf8, 0x5f, 0x6f, 0xe4, 0xb8, 0xfa, 0xab, 0xe7, 0xa2, 0x81, 0x33, 0x03, 0x97, 0x80, 0xdb, 0x7c, + 0xd0, 0xcd, 0x05, 0x33, 0x84, 0xec, 0x27, 0x9f, 0x0a, 0xb5, 0x9d, 0xcf, 0x7c, 0x34, 0x03, 0xe0, + 0x1c, 0x43, 0xc1, 0x65, 0x3f, 0x01, 0x9d, 0x1a, 0x7c, 0x9f, 0xf7, 0xc6, 0xed, 0x39, 0x52, 0x8e, + 0xf0, 0x0f, 0xa3, 0x74, 0x2d, 0xce, 0x93, 0x22, 0x75, 0xf0, 0x6d, 0x06, 0xfd, 0x54, 0xec, 0x1a, + 0x31, 0xc8, 0xe5, 0x21, 0xcb, 0xbf, 0xac, 0x88, 0xcb, 0x30, 0x3a, 0xf0, 0x3d, 0xe6, 0x65, 0x79, + 0x54, 0x69, 0xa1, 0x80, 0x56, 0x52, 0xda, 0x7a, 0x76, 0xef, 0xfb, 0x27, 0xc3, 0x92, 0x53, 0xca, + 0x19, 0x29, 0x73, 0x81, 0x1a, 0x96, 0xc8, 0x51, 0x10, 0x0b, 0x10, 0x93, 0x37, 0xb5, 0xb0, 0xe6, + 0xe4, 0x80, 0xdb, 0xe0, 0x91, 0x6b, 0x8c, 0x1c, 0x7e, 0x8e, 0x51, 0x6f, 0xee, 0xd5, 0x87, 0x0a, + 0x18, 0x7f, 0x50, 0x98, 0x3d, 0x9f, 0x92, 0x7b, 0xd5, 0x83, 0xeb, 0x30, 0x6d, 0x80, 0x40, 0x64, + 0xa9, 0x57, 0x15, 0x80, 0x5a, 0x4e, 0x7e, 0x98, 0x96, 0x4a, 0x8e, 0xc3, 0xfe, 0x4d, 0x6d, 0x02, + 0x1f, 0x5b, 0x6b, 0x99, 0x2f, 0x2d, 0x8a, 0x71, 0x60, 0x13, 0xc4, 0x38, 0x00, 0x82, 0x9a, 0xc3, + 0x02, 0x0a, 0x36, 0x0c, 0xb0, 0x12, 0x67, 0x86, 0x63, 0x85, 0x34, 0x87, 0x7d, 0x67, 0x47, 0xc7, + 0x73, 0xa3, 0x8f, 0x7b, 0x3e, 0xf8, 0x6d, 0x5b, 0x37, 0x4c, 0x29, 0xf6, 0x83, 0x2b, 0xd1, 0xf3, + 0xaa, 0x4f, 0xec, 0x6a, 0xad, 0xba, 0x49, 0x1a, 0x4e, 0x61, 0xb1, 0xb0, 0x6f, 0x05, 0xe4, 0x44, + 0x7c, 0x30, 0x36, 0x0a, 0x28, 0x4d, 0x60, 0xc0, 0xba, 0xb5, 0xcc, 0x5d, 0x5d, 0xbb, 0x60, 0x12, + 0x5f, 0x00, 0x3f, 0xa7, 0xf0, 0xaf, 0x34, 0x62, 0xdf, 0xdb, 0xff, 0x34, 0xde, 0x19, 0x27, 0x11, + 0x78, 0x01, 0xc2, 0x4d, 0xe8, 0xd1, 0x9e, 0xca, 0xff, 0x02, 0x2d, 0xbd, 0xf9, 0x10, 0x0b, 0xdd, + 0x1a, 0xdc, 0xac, 0x77, 0x64, 0x88, 0xb5, 0xeb, 0xba, 0xb7, 0xc0, 0xb7, 0x2d, 0xcf, 0x0e, 0x4a, + 0xa3, 0x56, 0x43, 0xd1, 0xb2, 0x92, 0x6e, 0x03, 0x2a, 0x1f, 0x9b, 0x4c, 0x5d, 0x65, 0x61, 0x80, + 0x41, 0x17, 0x86, 0x01, 0xcb, 0xc0, 0xcc, 0x73, 0xda, 0x9c, 0x9e, 0x5e, 0x93, 0x90, 0x11, 0xcf, + 0xc3, 0x0c, 0x95, 0x34, 0x83, 0x00, 0x02, 0xad, 0xcd, 0x1c, 0x23, 0xca, 0x90, 0xd1, 0x63, 0xd9, + 0xee, 0x50, 0x05, 0x28, 0xf2, 0xe6, 0x77, 0x4c, 0x1a, 0x94, 0xac, 0xed, 0x48, 0x62, 0xf6, 0xbb, + 0x76, 0xf8, 0x85, 0xe0, 0x4f, 0x3a, 0x2c, 0x19, 0xd8, 0xf1, 0x09, 0xc4, 0x36, 0x58, 0xdb, 0xaa, + 0x30, 0x0d, 0xf1, 0x65, 0xe0, 0x43, 0xa3, 0x7e, 0x50, 0x17, 0x05, 0x99, 0x87, 0xc4, 0x03, 0x84, + 0x83, 0x92, 0xd6, 0x67, 0x01, 0x20, 0x63, 0x78, 0x8a, 0xb4, 0xf7, 0xde, 0x75, 0x54, 0x85, 0x47, + 0x2f, 0xc3, 0x37, 0xc1, 0xaa, 0xa8, 0xde, 0xc9, 0x88, 0x86, 0xe0, 0xe4, 0xaa, 0x9d, 0x35, 0xba, + 0x49, 0xd1, 0x44, 0x1a, 0x5a, 0xc3, 0x62, 0xeb, 0xb6, 0x4f, 0x63, 0x7c, 0x3e, 0x8e, 0x4b, 0x2d, + 0xb0, 0xd6, 0xd8, 0x2f, 0x3a, 0xbc, 0x80, 0x56, 0xbf, 0xc1, 0x14, 0x23, 0x4f, 0x12, 0x72, 0xfe, + 0xed, 0x77, 0xd6, 0x52, 0x8b, 0x05, 0x3c, 0x7c, 0x0a, 0x1b, 0x04, 0x55, 0x21, 0x47, 0x1c, 0x16, + 0x25, 0x60, 0x86, 0x01, 0xad, 0x09, 0x1b, 0xe6, 0x66, 0x88, 0x47, 0x66, 0x12, 0x2e, 0x5c, 0x5a, + 0xd3, 0xe8, 0x3d, 0x76, 0xf4, 0x95, 0x4a, 0x3b, 0x4e, 0x8d, 0xb2, 0xc0, 0xca, 0x2a, 0xac, 0xbe, + 0x10, 0xbb, 0x5f, 0x9e, 0xd7, 0x9f, 0x25, 0x80, 0x69, 0x1b, 0xff, 0xcf, 0xe5, 0x42, 0xde, 0xcc, + 0xf0, 0xe3, 0x56, 0x85, 0x16, 0x53, 0x40, 0x60, 0x77, 0x55, 0xc8, 0x8b, 0xc0, 0x45, 0x80, 0xe7, + 0x00, 0x0f, 0x0d, 0x63, 0x0e, 0x5f, 0xb3, 0xc0, 0x73, 0x78, 0xf7, 0xa3, 0x97, 0x87, 0xc5, 0x1f, + 0x0e, 0x49, 0x93, 0x5e, 0x02, 0x50, 0x73, 0x70, 0xf2, 0xbf, 0xcd, 0x83, 0xa0, 0x81, 0x3f, 0x50, + 0x25, 0xd5, 0xeb, 0x00, 0xb7, 0xb2, 0xa7, 0x35, 0x7f, 0x7c, 0x44, 0x73, 0xf0, 0x72, 0x44, 0x86, + 0x69, 0x27, 0x8d, 0x20, 0x16, 0xf4, 0x15, 0x4d, 0x2e, 0x7e, 0xaf, 0xb2, 0x12, 0x81, 0xe6, 0x38, + 0xe4, 0xb4, 0xb4, 0xd7, 0xa9, 0xfc, 0x5a, 0x1c, 0xe9, 0x3d, 0x7a, 0xc4, 0xf6, 0x91, 0x2c, 0x98, + 0x00, 0x49, 0x3d, 0xce, 0x47, 0xc8, 0x8f, 0xbf, 0xc9, 0xed, 0xe7, 0x87, 0x1e, 0x75, 0x7d, 0xf6, + 0xbb, 0x4e, 0x99, 0x52, 0xc0, 0xd2, 0x48, 0x25, 0xc4, 0xc5, 0x93, 0x97, 0xdb, 0x4b, 0x87, 0x33, + 0xca, 0x64, 0x50, 0x0a, 0x30, 0xa6, 0x50, 0x94, 0xfe, 0x56, 0xa4, 0xbb, 0xc0, 0x24, 0x56, 0xb0, + 0xd7, 0x55, 0x74, 0x0c, 0x90, 0x6d, 0xe7, 0x4a, 0x00, 0xa0, 0xee, 0x3c, 0x51, 0xdf, 0xf0, 0xec, + 0x5b, 0xfc, 0xbf, 0xf1, 0xb9, 0xb4, 0x98, 0x00, 0xda, 0x55, 0x45, 0xa8, 0xf0, 0x02, 0xa8, 0xd6, + 0xe0, 0xe4, 0x54, 0x4f, 0x5d, 0x75, 0xb1, 0xdc, 0xf2, 0x22, 0x80, 0xff, 0xeb, 0x13, 0xf7, 0xb1, + 0x14, 0xed, 0x07, 0x24, 0xd1, 0xab, 0xe1, 0xc8, 0x53, 0x52, 0x60, 0x25, 0xc8, 0x89, 0xe0, 0x30, + 0xd2, 0xa4, 0x61, 0xf6, 0x65, 0xc7, 0x3a, 0x19, 0x79, 0x2a, 0x35, 0x03, 0x22, 0x88, 0x65, 0x1f, + 0x92, 0x65, 0x2d, 0xb1, 0x71, 0xc2, 0x04, 0x16, 0x96, 0x18, 0x2c, 0xa4, 0xd0, 0xc0, 0xdc, 0xac, + 0x3d, 0xb7, 0x82, 0xb2, 0x3f, 0xc0, 0x1b, 0xff, 0xae, 0x5c, 0xb8, 0x63, 0xd2, 0xb3, 0xd0, 0x55, + 0xfa, 0xb0, 0xf3, 0xd1, 0xc2, 0x55, 0xb5, 0x6a, 0x5d, 0x60, 0x3d, 0x94, 0xa4, 0x26, 0x6f, 0x5e, + 0x8e, 0x01, 0xf8, 0xe1, 0xd4, 0x1a, 0x0c, 0xd7, 0x32, 0xcd, 0xf0, 0xdc, 0x70, 0x04, 0x61, 0x42, + 0xd0, 0x81, 0xaf, 0x12, 0x04, 0x54, 0x76, 0x18, 0x3a, 0x57, 0x09, 0xbe, 0x5b, 0xa8, 0xf0, 0x72, + 0xa8, 0xb6, 0x60, 0xe4, 0xc2, 0xb5, 0x01, 0x1b, 0x87, 0x91, 0x24, 0x5a, 0xbf, 0x07, 0x95, 0x1d, + 0xac, 0x1c, 0xcf, 0x66, 0x8d, 0x38, 0x16, 0xf4, 0x0a, 0xe5, 0xd9, 0x58, 0x2f, 0x16, 0x6d, 0x04, + 0x18, 0x9a, 0x06, 0x01, 0xdc, 0xff, 0x1c, 0xec, 0x23, 0x7c, 0x19, 0xfc, 0x6a, 0x73, 0x9e, 0xbb, + 0x9c, 0x01, 0x40, 0x7c, 0xc1, 0x80, 0x5d, 0x46, 0x98, 0xe1, 0x92, 0x04, 0xe7, 0x32, 0xc4, 0x1d, + 0xb8, 0x0e, 0x23, 0x4f, 0x41, 0xc5, 0x81, 0xba, 0xfb, 0x3f, 0xad, 0xb6, 0x94, 0x76, 0xdc, 0x9e, + 0x0f, 0x9b, 0x33, 0xcf, 0xff, 0xed, 0xbf, 0xf3, 0xd1, 0xc1, 0x1f, 0x59, 0x54, 0xf0, 0xa7, 0x9d, + 0x1d, 0x3c, 0x5f, 0x28, 0x33, 0x78, 0x9f, 0x85, 0x58, 0x8b, 0x6e, 0xad, 0x23, 0x17, 0x60, 0xc0, + 0x10, 0x45, 0x16, 0xa0, 0xe4, 0x44, 0x7c, 0x2c, 0x94, 0x1d, 0x24, 0x5a, 0xbf, 0x00, 0x95, 0x17, + 0x7c, 0x1c, 0x55, 0x40, 0x73, 0x63, 0x44, 0x0a, 0x74, 0xe4, 0xbc, 0xe0, 0xec, 0x7a, 0x11, 0xdd, + 0x39, 0x2f, 0x24, 0xb8, 0x49, 0xd1, 0x4c, 0x1a, 0xba, 0x43, 0x22, 0x74, 0x0a, 0x32, 0x9a, 0xaf, + 0x00, 0x0d, 0x66, 0xd3, 0x69, 0x65, 0x6a, 0x1f, 0x80, 0x41, 0xc5, 0xe1, 0xdc, 0xc8, 0x5f, 0xac, + 0xa1, 0x80, 0x90, 0xee, 0x85, 0x78, 0x1f, 0x22, 0x7c, 0x1b, 0x69, 0xdb, 0x35, 0x87, 0x8c, 0xcb, + 0x31, 0x69, 0x7c, 0xbf, 0x99, 0xa3, 0xe3, 0xf9, 0x1c, 0xce, 0x82, 0x3b, 0xd7, 0x2f, 0xa5, 0x83, + 0xfa, 0xb9, 0x31, 0x62, 0xf2, 0x96, 0x85, 0xb2, 0x04, 0x94, 0x8c, 0x18, 0xff, 0xcd, 0x2a, 0x42, + 0x0c, 0x02, 0xa9, 0x4f, 0xbd, 0x6a, 0xc0, 0x82, 0x9b, 0x4d, 0x25, 0x80, 0x87, 0x22, 0x5b, 0xe0, + 0x6b, 0x77, 0xba, 0xa3, 0x40, 0xdd, 0x03, 0x9d, 0x19, 0x11, 0xbc, 0x0d, 0xc6, 0x89, 0x35, 0xd6, + 0xa3, 0x88, 0xaf, 0x17, 0xad, 0x3d, 0xb0, 0x26, 0x42, 0x61, 0xb0, 0x72, 0x08, 0x9a, 0xdd, 0x42, + 0x83, 0x92, 0x45, 0x37, 0x3a, 0x97, 0x93, 0x55, 0xb4, 0x38, 0xa3, 0x42, 0x2a, 0x62, 0xe2, 0x67, + 0x4d, 0x1b, 0x98, 0xdb, 0x5d, 0xff, 0xd4, 0xfe, 0x02, 0x80, 0x81, 0x0c, 0xaa, 0x06, 0xa2, 0x96, + 0xab, 0x20, 0xe4, 0x96, 0xeb, 0xe9, 0x1a, 0xae, 0xb2, 0xae, 0x0c, 0xbf, 0x42, 0xa4, 0xd6, 0x02, + 0x16, 0x39, 0x4a, 0x9c, 0x73, 0x33, 0xb6, 0xcf, 0xf8, 0x48, 0x1e, 0xf9, 0xe7, 0xdf, 0xb3, 0xb4, + 0xa1, 0x85, 0x23, 0x81, 0x62, 0xe0, 0x4b, 0xf2, 0x76, 0xb9, 0xc4, 0xf2, 0x61, 0x56, 0x7d, 0xba, + 0x86, 0x01, 0x17, 0x86, 0x48, 0xe0, 0x14, 0x61, 0x07, 0xbf, 0x5c, 0x58, 0xd0, 0xe1, 0xfc, 0xb3, + 0x56, 0x1b, 0xe7, 0x46, 0x9e, 0x15, 0x11, 0x19, 0x12, 0x39, 0x57, 0xf8, 0xdd, 0xe0, 0x84, 0xf8, + 0x0c, 0x6e, 0x27, 0xd3, 0x9f, 0x22, 0xd5, 0x50, 0x71, 0x48, 0xa2, 0x8a, 0xe0, 0xe4, 0xa5, 0x85, + 0x5a, 0xed, 0x00, 0x24, 0xb2, 0x7f, 0x9f, 0xa5, 0x41, 0xf5, 0xc1, 0xc9, 0x55, 0x47, 0xf1, 0xf5, + 0x63, 0xb8, 0xc9, 0x8b, 0x17, 0x70, 0x63, 0xd5, 0xee, 0xef, 0xc9, 0x78, 0x47, 0xc1, 0x9d, 0x13, + 0x00, 0xc4, 0xf2, 0x8d, 0xf6, 0x18, 0x7f, 0xe7, 0xcc, 0xab, 0x86, 0xcd, 0xa8, 0x1b, 0x13, 0xd1, + 0x4c, 0x33, 0x96, 0x98, 0x81, 0xbc, 0x44, 0x1a, 0x0f, 0x91, 0x21, 0xab, 0x98, 0xaa, 0x92, 0x04, + 0xaf, 0x32, 0x75, 0x1b, 0x26, 0xf0, 0xf5, 0x60, 0xe0, 0x16, 0x60, 0xfe, 0xb9, 0xf8, 0x78, 0x13, + 0xb7, 0xd0, 0xb6, 0x9d, 0xc3, 0x00, 0x55, 0x46, 0x59, 0x5d, 0xf0, 0xe1, 0x38, 0x5a, 0xcb, 0x01, + 0xec, 0xa2, 0x02, 0x31, 0xf5, 0x0c, 0xff, 0xae, 0xe2, 0xcd, 0xa3, 0x09, 0x70, 0x00, 0x7b, 0x78, + 0x29, 0x86, 0x05, 0x86, 0x62, 0x18, 0x05, 0x84, 0x09, 0xaa, 0x58, 0x30, 0xff, 0x9d, 0x10, 0x2d, + 0xb7, 0x79, 0xfb, 0xd7, 0x83, 0x92, 0x07, 0x6e, 0x6c, 0xde, 0x02, 0xf4, 0xb4, 0xa2, 0xb7, 0x01, + 0xc8, 0x8b, 0x55, 0x14, 0x2e, 0xbd, 0x6f, 0xf4, 0x4b, 0x75, 0xc6, 0x00, 0x0e, 0x48, 0x1b, 0x56, + 0x5e, 0x8d, 0xaf, 0x5d, 0x6f, 0xbd, 0x88, 0xc4, 0x0c, 0x2e, 0x04, 0xfa, 0x69, 0x06, 0x53, 0xd4, + 0x50, 0x15, 0xb2, 0x40, 0x82, 0x51, 0x9a, 0xb2, 0x54, 0x73, 0x15, 0x55, 0xf2, 0x91, 0x3c, 0xd3, + 0x92, 0x66, 0xf4, 0xf0, 0x92, 0xef, 0xf8, 0x97, 0x26, 0x7c, 0x1d, 0xfa, 0x93, 0xbf, 0x08, 0x0f, + 0x9c, 0x30, 0x21, 0x62, 0xb5, 0x44, 0xab, 0xb4, 0x00, 0x8a, 0x85, 0x83, 0xfa, 0x1e, 0xbb, 0xc3, + 0x54, 0x72, 0x80, 0x18, 0xca, 0xa7, 0xae, 0xe2, 0xf1, 0x48, 0x07, 0xfa, 0xd3, 0x43, 0x56, 0x11, + 0x4a, 0x3b, 0x02, 0x31, 0x66, 0x0c, 0x7f, 0xe7, 0x4c, 0x9b, 0x7d, 0x94, 0xcc, 0x90, 0x46, 0x06, + 0x36, 0x94, 0x70, 0x9a, 0x58, 0x59, 0x1e, 0xe0, 0x5e, 0x9b, 0x15, 0xe2, 0xae, 0x23, 0x1a, 0x52, + 0xbe, 0xfd, 0xfb, 0x15, 0xd7, 0xce, 0xc5, 0xcf, 0x42, 0x78, 0x00, 0x36, 0xc8, 0x88, 0xea, 0x54, + 0x1c, 0x94, 0x7f, 0xbc, 0x2e, 0x7b, 0x44, 0x5c, 0x09, 0x64, 0xdb, 0xf0, 0x63, 0xb8, 0xcb, 0x8b, + 0x3f, 0x5b, 0x8f, 0x50, 0xdf, 0xbd, 0xc8, 0xec, 0x7c, 0xf1, 0xde, 0xd0, 0xc0, 0xf3, 0x89, 0x03, + 0x62, 0x4e, 0xba, 0x88, 0x65, 0x3d, 0xc0, 0xb1, 0x9c, 0xc1, 0xf6, 0xd2, 0xa3, 0xa4, 0x8c, 0x32, + 0x60, 0x9a, 0x18, 0x36, 0xcf, 0x9a, 0xe6, 0x45, 0x57, 0x66, 0xe6, 0x85, 0x01, 0x66, 0x51, 0x96, + 0x61, 0xec, 0xb2, 0xbf, 0xcf, 0x28, 0x97, 0x8d, 0x5d, 0xd5, 0x6c, 0x65, 0xc1, 0x23, 0x1f, 0x22, + 0x05, 0xd6, 0x2a, 0x18, 0x3f, 0x2d, 0xe0, 0x12, 0xc3, 0x78, 0xbf, 0xf3, 0x85, 0x79, 0x96, 0x8b, + 0x87, 0x75, 0x48, 0x33, 0x95, 0x9b, 0x82, 0xb2, 0x58, 0x4a, 0xc1, 0x80, 0x47, 0x46, 0x46, 0x29, + 0xc1, 0x80, 0x03, 0xa8, 0xc7, 0x3e, 0x00, 0xbe, 0xb2, 0xc3, 0x14, 0x06, 0x48, 0x2e, 0xfc, 0x3b, + 0x95, 0x0c, 0x7f, 0xe7, 0xf0, 0x3b, 0xec, 0x0a, 0x9e, 0x00, 0x97, 0x8a, 0xa2, 0xe9, 0x55, 0x2d, + 0x38, 0x3a, 0xfb, 0xe7, 0x7e, 0x77, 0x72, 0x6a, 0xc7, 0x1b, 0x4e, 0x18, 0xbc, 0x20, 0x4b, 0x6b, + 0x5f, 0xbf, 0xf1, 0x0e, 0x11, 0x68, 0xf8, 0xc9, 0x00, 0xd6, 0x90, 0x9b, 0xbc, 0x00, 0xca, 0xbe, + 0xf2, 0x7a, 0xe1, 0x4f, 0xc6, 0x8d, 0xb6, 0x6b, 0x7d, 0x77, 0x7b, 0x32, 0x69, 0x56, 0x50, 0xc4, + 0x50, 0x35, 0xc0, 0xe0, 0xe8, 0xc6, 0xf5, 0x77, 0x73, 0xc0, 0x00, 0xb3, 0xe1, 0x0f, 0x71, 0x0b, + 0x80, 0x35, 0x09, 0x7f, 0x44, 0x2d, 0xbb, 0x4f, 0x70, 0xae, 0x69, 0x99, 0x19, 0xed, 0xaa, 0xe3, + 0x31, 0x3e, 0x37, 0xcb, 0x95, 0x9a, 0xc0, 0x05, 0xbe, 0x7d, 0xb4, 0xed, 0x72, 0xd7, 0x41, 0x82, + 0xa4, 0x05, 0x19, 0x21, 0xc9, 0x38, 0x28, 0xb0, 0xd3, 0xfb, 0x01, 0x51, 0x86, 0x86, 0x7f, 0xd6, + 0xa6, 0x86, 0x60, 0x13, 0x83, 0x98, 0xd1, 0xaf, 0xb9, 0x4f, 0x10, 0x73, 0xb6, 0x28, 0x2f, 0xf2, + 0xda, 0x53, 0x6d, 0x19, 0xbd, 0x78, 0x77, 0xf7, 0x05, 0x36, 0x7f, 0x22, 0x78, 0x2d, 0x14, 0x62, + 0xc5, 0xdd, 0x5f, 0xa8, 0x12, 0x5d, 0x7f, 0x80, 0x9d, 0xd9, 0x41, 0x67, 0xd4, 0x30, 0x1a, 0xbe, + 0x59, 0xf8, 0xb1, 0xdc, 0xa4, 0xc5, 0xbf, 0xb3, 0xc7, 0xaf, 0x05, 0x5e, 0xc1, 0xc9, 0x26, 0x9f, + 0x89, 0x3a, 0x1a, 0x41, 0x6c, 0xd1, 0xc2, 0x07, 0x69, 0xcf, 0xff, 0xaf, 0xa1, 0x2d, 0x5f, 0xaa, + 0xeb, 0xc0, 0xd5, 0x3d, 0x34, 0x1b, 0x01, 0xdd, 0x08, 0xdc, 0x15, 0xdc, 0x6f, 0xda, 0x44, 0x35, + 0x95, 0x05, 0xb0, 0x13, 0x58, 0x66, 0x7e, 0xa8, 0x4d, 0x55, 0xb0, 0xfe, 0xc8, 0x49, 0xcd, 0xe3, + 0x5e, 0xfb, 0x9a, 0x1a, 0x9b, 0xd7, 0x3f, 0xe9, 0x4c, 0xe8, 0x1d, 0x0b, 0x67, 0xa5, 0x0d, 0x82, + 0x17, 0x4c, 0x35, 0x5e, 0x86, 0xa1, 0x22, 0x20, 0xf1, 0x23, 0x9c, 0x55, 0x21, 0xb3, 0xf0, 0xc0, + 0x67, 0x4c, 0xc3, 0xc3, 0xe0, 0xcf, 0x44, 0xeb, 0xcc, 0x8c, 0xb3, 0x0a, 0x77, 0x0c, 0x76, 0x22, + 0x95, 0xe7, 0xc1, 0xa5, 0xb1, 0xdf, 0x82, 0xb8, 0x00, 0xb0, 0xae, 0x58, 0x0c, 0xff, 0x02, 0xf1, + 0xe1, 0x0c, 0x2b, 0x62, 0x0f, 0xc7, 0x18, 0xcd, 0xbe, 0x77, 0xee, 0x15, 0x49, 0x53, 0xa1, 0x14, + 0xc9, 0x6e, 0x98, 0xc1, 0xef, 0xd1, 0xbe, 0x0d, 0xd7, 0x5a, 0xfe, 0x61, 0xdf, 0x11, 0xc5, 0x34, + 0x2a, 0xfe, 0x58, 0x39, 0x46, 0x8d, 0x59, 0x3c, 0x58, 0xcd, 0x0b, 0x01, 0x55, 0x00, 0x49, 0xbd, + 0xfe, 0x44, 0x37, 0x8c, 0xae, 0x12, 0x7b, 0x25, 0x03, 0x70, 0x09, 0x5e, 0xc0, 0xb6, 0x8b, 0x72, + 0x00, 0xb0, 0x8d, 0x83, 0xd5, 0x10, 0xbe, 0xda, 0x38, 0x42, 0xaf, 0x8b, 0xb1, 0xcc, 0x54, 0x16, + 0x62, 0x86, 0xff, 0x9c, 0x03, 0xbf, 0x8d, 0x9b, 0x1e, 0x1b, 0x3d, 0x1c, 0x6b, 0x78, 0xd7, 0x08, + 0xfc, 0xc7, 0x24, 0xdb, 0xc6, 0xff, 0xed, 0x11, 0xa7, 0x86, 0x14, 0x47, 0x0e, 0x5e, 0xf6, 0x96, + 0xb1, 0x79, 0x03, 0x4a, 0x0b, 0x53, 0xfd, 0x93, 0x47, 0x1b, 0x6c, 0x25, 0x6a, 0x90, 0xf4, 0x21, + 0x00, 0x7b, 0xd6, 0x96, 0xbe, 0x73, 0x3c, 0xe2, 0xd4, 0xe2, 0xf6, 0xd2, 0x23, 0xe9, 0x0d, 0xb2, + 0x4a, 0x2f, 0xe6, 0x1f, 0x3d, 0x89, 0x42, 0x79, 0x62, 0xbf, 0x02, 0xf0, 0x86, 0x0c, 0xd7, 0x3b, + 0x04, 0x95, 0x44, 0x18, 0x5c, 0xe4, 0x6d, 0x7c, 0x8d, 0xb4, 0x16, 0x55, 0xa3, 0xf3, 0xa9, 0xf7, + 0x4b, 0x54, 0x20, 0x3d, 0x28, 0x39, 0x97, 0x24, 0x5c, 0x00, 0x99, 0xdc, 0x7f, 0xe7, 0x92, 0x82, + 0x5b, 0x83, 0x3b, 0x81, 0x8e, 0xe3, 0x4e, 0x2d, 0x7c, 0x03, 0x1e, 0xb2, 0xcd, 0x7b, 0x39, 0x22, + 0x8d, 0x58, 0xfe, 0xba, 0xc5, 0xa2, 0xe3, 0x6c, 0xc3, 0xf2, 0x72, 0x3a, 0x30, 0xbc, 0xba, 0x69, + 0x06, 0xf9, 0x46, 0x21, 0xa1, 0x80, 0x83, 0x62, 0xaa, 0xa0, 0xd8, 0x8b, 0xe7, 0x27, 0x28, 0xd5, + 0xd7, 0xec, 0xae, 0x15, 0xfd, 0x92, 0x63, 0x64, 0x1c, 0x04, 0xab, 0x23, 0x1b, 0x77, 0xf1, 0x5e, + 0x34, 0x93, 0x01, 0xc9, 0x06, 0xdb, 0xcf, 0x98, 0xc4, 0x39, 0x31, 0xbf, 0x70, 0xde, 0x69, 0x47, + 0x6f, 0x75, 0xe0, 0x81, 0x0a, 0xea, 0x89, 0x26, 0x4d, 0xc8, 0x3a, 0xc0, 0xf0, 0xbc, 0x06, 0xd7, + 0xfb, 0xbc, 0xc7, 0x97, 0x7e, 0x4b, 0xfb, 0xcd, 0x8b, 0xfe, 0xee, 0xdb, 0xd5, 0x54, 0xbe, 0x5d, + 0x05, 0xf3, 0x8f, 0x7e, 0x8e, 0xe9, 0x78, 0x1f, 0xcf, 0xfa, 0xae, 0x21, 0x91, 0x15, 0x80, 0x17, + 0x78, 0x55, 0xc4, 0x21, 0x44, 0xdd, 0xbf, 0xaf, 0x6f, 0xd2, 0x11, 0xcb, 0x9a, 0x8a, 0x34, 0xa5, + 0xf0, 0x2d, 0x51, 0x42, 0x31, 0xce, 0xf2, 0x8e, 0xa4, 0x12, 0x16, 0x71, 0xb6, 0x8a, 0x49, 0x54, + 0x6b, 0x0e, 0x79, 0xa6, 0x8f, 0xf2, 0xe3, 0x00, 0x3b, 0x4b, 0xaf, 0xcf, 0x02, 0x41, 0xeb, 0xf0, + 0x75, 0xd1, 0xb7, 0xb8, 0x92, 0x86, 0xfd, 0x25, 0x08, 0x7c, 0xb3, 0xdf, 0x8b, 0x5e, 0x66, 0xbd, + 0x1c, 0x91, 0x46, 0xac, 0x5d, 0xbd, 0xe3, 0xaa, 0x15, 0xf1, 0xb7, 0x04, 0xff, 0x31, 0x65, 0xbb, + 0x81, 0x4d, 0x9b, 0x9d, 0x0d, 0x02, 0xfc, 0xc2, 0x91, 0x70, 0x0b, 0x70, 0x1d, 0xa4, 0x0a, 0xa2, + 0xba, 0x60, 0x58, 0x81, 0x5d, 0x13, 0xd1, 0xbe, 0xe7, 0x81, 0x9a, 0x4c, 0xed, 0x81, 0x72, 0x22, + 0xaa, 0xa9, 0x66, 0xc6, 0x12, 0x51, 0x58, 0x72, 0x5a, 0x13, 0xde, 0xcc, 0x18, 0xab, 0x75, 0x67, + 0xbe, 0x28, 0x7c, 0x9e, 0x88, 0x2d, 0x42, 0xc8, 0xe2, 0xe0, 0x61, 0x62, 0xe0, 0x84, 0x69, 0xef, + 0xdf, 0x35, 0x5a, 0xf9, 0xfd, 0x19, 0x17, 0x4d, 0xf2, 0x37, 0x29, 0xfc, 0xe0, 0x39, 0x17, 0x6f, + 0xab, 0xff, 0x8d, 0x8a, 0x2d, 0xa2, 0xd6, 0x55, 0x37, 0x8c, 0xa1, 0x43, 0xb9, 0x9f, 0x90, 0x8d, + 0x7c, 0x1c, 0xb7, 0xa5, 0x5e, 0x10, 0xdf, 0xab, 0x75, 0x49, 0xc1, 0xc9, 0xd4, 0xcf, 0xaf, 0x63, + 0x24, 0x99, 0xab, 0x07, 0xef, 0x51, 0x9b, 0xdb, 0xfa, 0xc2, 0xe3, 0x5f, 0xa6, 0x19, 0x21, 0x83, + 0x04, 0xed, 0x00, 0x92, 0x47, 0xa6, 0xd5, 0xc0, 0x7f, 0x9c, 0x30, 0x13, 0x78, 0x16, 0x44, 0x40, + 0xaa, 0xae, 0xef, 0xf0, 0x4b, 0x13, 0x46, 0x1f, 0x05, 0x64, 0x11, 0xd3, 0x13, 0xdb, 0x66, 0x86, + 0x30, 0x31, 0x77, 0x4d, 0xf0, 0x32, 0x88, 0xa4, 0x8d, 0x9c, 0xf7, 0xef, 0x57, 0x80, 0xc3, 0x84, + 0x27, 0x34, 0x9c, 0x1b, 0x6d, 0x9f, 0x98, 0x11, 0xf8, 0x63, 0xc9, 0xfd, 0x2c, 0x29, 0xf1, 0xa8, + 0x4e, 0x19, 0x5c, 0x41, 0x00, 0xa5, 0x17, 0xcc, 0x0c, 0x91, 0x5a, 0xbe, 0x93, 0x06, 0x84, 0xdb, + 0x0c, 0xa3, 0x35, 0x4f, 0xdc, 0xc5, 0x35, 0xa7, 0x61, 0x45, 0xf7, 0x84, 0xd3, 0xe1, 0xae, 0x6c, + 0x02, 0x55, 0x94, 0x30, 0x79, 0xc3, 0xaa, 0x87, 0xab, 0x6e, 0xf8, 0x70, 0x80, 0x7b, 0xea, 0xa3, + 0xd7, 0xb5, 0x92, 0x2c, 0xd5, 0x83, 0xac, 0x68, 0xb6, 0x03, 0xfe, 0xb2, 0x5c, 0xd7, 0xa1, 0x85, + 0x04, 0x04, 0x54, 0x42, 0x1a, 0xc7, 0x80, 0x76, 0xaa, 0x7b, 0x30, 0x08, 0xcb, 0x0c, 0x87, 0x08, + 0x2e, 0x19, 0x10, 0x60, 0x05, 0x53, 0xd9, 0xa9, 0xe0, 0xc8, 0xb4, 0x49, 0x51, 0xc5, 0xd0, 0x8e, + 0x33, 0x8c, 0xc0, 0x9c, 0x70, 0x02, 0x9b, 0x58, 0xdb, 0xf0, 0x43, 0x99, 0x75, 0x39, 0xb6, 0x78, + 0x99, 0xd1, 0xe3, 0xe9, 0x8f, 0x80, 0x92, 0x12, 0x9b, 0xa0, 0xc1, 0x60, 0xdf, 0x01, 0x64, 0x7c, + 0x1f, 0x07, 0xc4, 0x3e, 0xe3, 0xbb, 0x00, 0x58, 0x0f, 0x00, 0x49, 0x3b, 0x41, 0x44, 0xa4, 0x54, + 0x88, 0xd5, 0xd7, 0xad, 0xcd, 0x0c, 0x2a, 0x26, 0x50, 0xaa, 0x8b, 0x0b, 0xf2, 0xbb, 0x16, 0x74, + 0xcb, 0x6a, 0x37, 0xe0, 0x72, 0x2d, 0x23, 0x11, 0xb2, 0xae, 0x28, 0xa4, 0x30, 0xc0, 0x18, 0x0b, + 0xda, 0x55, 0x63, 0x69, 0xc5, 0x95, 0xce, 0xa2, 0x7d, 0xc8, 0xb5, 0x2a, 0x75, 0xad, 0x00, 0x49, + 0x89, 0x6e, 0x0e, 0x4a, 0x38, 0xe6, 0x24, 0x88, 0xab, 0x07, 0xef, 0x51, 0xb5, 0x5f, 0x80, 0xec, + 0x87, 0xdb, 0x87, 0x0a, 0xb6, 0xcf, 0xc0, 0x20, 0x1c, 0x10, 0x87, 0x01, 0x18, 0x05, 0xd3, 0x8c, + 0x02, 0x6c, 0x43, 0x0c, 0x18, 0x32, 0x3f, 0xb7, 0x45, 0xb6, 0x24, 0xb8, 0xe1, 0x61, 0x1b, 0x73, + 0xd7, 0x3c, 0x67, 0x95, 0x57, 0x69, 0x3c, 0x39, 0x5e, 0x3a, 0xc6, 0x44, 0x28, 0xc4, 0x95, 0x0e, + 0x5d, 0x33, 0x85, 0x43, 0x8d, 0xd8, 0xc6, 0xf1, 0x2d, 0x51, 0x31, 0x3c, 0x7b, 0xdb, 0xb6, 0xb5, + 0x94, 0xe6, 0xbd, 0x75, 0xd6, 0x05, 0x2a, 0xf4, 0xfa, 0x6b, 0xbc, 0x0a, 0xa7, 0xda, 0x54, 0xe4, + 0x6b, 0xc9, 0x72, 0x71, 0x8b, 0x54, 0xa3, 0x93, 0x79, 0x0a, 0x7d, 0xd5, 0x81, 0x9f, 0xc2, 0x82, + 0x77, 0x1f, 0xd8, 0xe5, 0x5f, 0xd2, 0xee, 0xbe, 0x84, 0x19, 0x10, 0x04, 0xfc, 0x6c, 0x71, 0xa6, + 0xc2, 0x2d, 0xd3, 0xed, 0x1c, 0xbd, 0x9e, 0xc6, 0x97, 0x82, 0xb8, 0x10, 0xb7, 0xae, 0x9a, 0xc0, + 0x65, 0x3e, 0x6f, 0x6d, 0xce, 0x81, 0xc6, 0xbc, 0x96, 0xfc, 0x67, 0xd2, 0xfc, 0x0d, 0x95, 0x9e, + 0x05, 0xbd, 0xfb, 0xe6, 0x74, 0x3a, 0x56, 0x36, 0xec, 0x0f, 0x69, 0xca, 0xcf, 0xcd, 0xa9, 0xef, + 0xb3, 0x86, 0x63, 0xb3, 0xed, 0xde, 0x0b, 0x66, 0x5f, 0x3f, 0xaf, 0x80, 0x7f, 0xce, 0x92, 0x4d, + 0xaf, 0x83, 0x11, 0xf6, 0x83, 0xac, 0x4f, 0xc1, 0x49, 0x2b, 0x16, 0x40, 0x8f, 0x0b, 0xcb, 0xec, + 0x6e, 0xfc, 0xe4, 0x8c, 0xcf, 0xe0, 0x94, 0xd3, 0xac, 0x1c, 0xcd, 0x46, 0x58, 0x0e, 0xa6, 0x44, + 0x47, 0x00, 0xb6, 0x94, 0x9f, 0xa7, 0x25, 0x9a, 0xb8, 0x6b, 0xd6, 0x93, 0x5b, 0xb5, 0xb7, 0x59, + 0xf0, 0x2c, 0x2c, 0x90, 0x61, 0x4a, 0xbd, 0x52, 0xea, 0x19, 0x97, 0x0a, 0x91, 0xb1, 0x75, 0x05, + 0xd4, 0xdd, 0x3d, 0x64, 0x5b, 0x50, 0x70, 0x7c, 0xb7, 0x45, 0x01, 0xdf, 0x37, 0x83, 0x1a, 0xdf, + 0xc0, 0x76, 0x81, 0x2b, 0xc1, 0x63, 0x62, 0x5b, 0x35, 0xb1, 0x89, 0xa7, 0xf2, 0x17, 0xc3, 0xd7, + 0x16, 0xf8, 0x7c, 0x61, 0x96, 0x4b, 0x86, 0x93, 0x7c, 0x0d, 0x1c, 0x6c, 0x3e, 0x87, 0x27, 0xb5, + 0x09, 0xb0, 0xf7, 0x2c, 0x88, 0xef, 0x50, 0xc9, 0x4a, 0x36, 0xc6, 0x47, 0xfa, 0xcb, 0xa6, 0x3f, + 0x70, 0xc6, 0xbf, 0xe8, 0xed, 0xae, 0x38, 0x0d, 0xa4, 0xe0, 0x4a, 0xcb, 0xe1, 0x6a, 0xa3, 0x0d, + 0x78, 0x5a, 0x93, 0x43, 0x6a, 0x96, 0x52, 0xad, 0x23, 0x80, 0x5d, 0x10, 0x5d, 0x34, 0xa5, 0x6c, + 0xb5, 0xca, 0xcb, 0x7c, 0x23, 0xbf, 0x04, 0xa9, 0xde, 0xe0, 0x72, 0xbc, 0x32, 0x5b, 0xc0, 0x09, + 0x4b, 0x46, 0x42, 0x0e, 0x94, 0x93, 0x8c, 0x9d, 0x9e, 0xa3, 0xdf, 0xa7, 0xf8, 0x7a, 0xc5, 0x79, + 0x39, 0x53, 0xe9, 0x42, 0xe9, 0x56, 0xff, 0xcf, 0x48, 0x5a, 0x8a, 0xc0, 0x31, 0x79, 0x95, 0x36, + 0xf4, 0x3e, 0x4e, 0xdb, 0x6b, 0x3f, 0x42, 0x22, 0xd3, 0x73, 0xc0, 0xdd, 0x31, 0x4b, 0xad, 0x87, + 0x6f, 0xf0, 0xdf, 0x3c, 0xff, 0xb5, 0xb0, 0xdc, 0x3d, 0xe5, 0x36, 0x05, 0x07, 0xf7, 0xf2, 0x7a, + 0x08, 0x8b, 0xc6, 0xfd, 0x89, 0x50, 0xa6, 0xbd, 0xfc, 0x27, 0x5b, 0x2c, 0x0b, 0xec, 0x25, 0x35, + 0x49, 0x83, 0x1a, 0xc0, 0x11, 0x9a, 0x34, 0x4e, 0x12, 0x3c, 0x88, 0x87, 0xf3, 0xde, 0x60, 0x1e, + 0xbe, 0x38, 0xf5, 0x8c, 0x24, 0x9a, 0x37, 0x07, 0xc7, 0xf7, 0x94, 0xa9, 0x17, 0x3d, 0xac, 0x77, + 0xbc, 0x88, 0x78, 0x16, 0x3d, 0x43, 0x9a, 0xf6, 0x72, 0x51, 0x1a, 0xb0, 0x95, 0xfb, 0xae, 0x03, + 0xc0, 0x26, 0x91, 0x60, 0x5d, 0x40, 0xd3, 0x61, 0xaa, 0x21, 0x01, 0x18, 0xd5, 0x86, 0xb5, 0x78, + 0x01, 0x4c, 0xf8, 0x4f, 0x52, 0xbd, 0xe8, 0x7d, 0x99, 0x03, 0x7d, 0x8d, 0xce, 0x1a, 0x7d, 0xce, + 0xc2, 0xb2, 0xfb, 0x02, 0xfc, 0x67, 0xbf, 0x1b, 0xaa, 0x8c, 0x35, 0x26, 0x15, 0x2d, 0x84, 0x96, + 0xc4, 0xac, 0xf6, 0x5b, 0xc0, 0x03, 0x77, 0x6f, 0xc2, 0xe4, 0xbd, 0xe1, 0x25, 0xa6, 0x84, 0xaf, + 0x20, 0xab, 0x54, 0xf1, 0x71, 0x05, 0x83, 0x91, 0x98, 0x81, 0x7b, 0x77, 0xaa, 0xbd, 0x2e, 0xae, + 0x3f, 0xd6, 0x27, 0x2e, 0xcd, 0x3d, 0x61, 0x19, 0xb1, 0x24, 0xc0, 0x2f, 0xbd, 0x2b, 0x36, 0x05, + 0xd7, 0x06, 0x3d, 0xcf, 0x27, 0x8e, 0x50, 0x82, 0x16, 0xa6, 0xc0, 0x09, 0xa3, 0x9b, 0x8f, 0x4b, + 0x4b, 0x6c, 0x56, 0x0e, 0x97, 0xb3, 0xf5, 0x3a, 0xe0, 0x38, 0xcf, 0x72, 0x93, 0x28, 0xa3, 0x85, + 0x6b, 0x5f, 0xfe, 0x61, 0x10, 0x77, 0xb0, 0x15, 0x92, 0x4c, 0x00, 0x86, 0x92, 0xc3, 0x09, 0x68, + 0xb9, 0xc8, 0xc5, 0x78, 0x01, 0xcb, 0x9e, 0x2f, 0xf4, 0xf3, 0xd8, 0x41, 0xcd, 0x88, 0xd6, 0x8b, + 0xae, 0x5b, 0x1e, 0x54, 0x7e, 0xf1, 0xec, 0xa0, 0xef, 0x75, 0xd6, 0xd9, 0xce, 0x1f, 0x6c, 0x8e, + 0xe6, 0x5a, 0x15, 0x46, 0x1e, 0x88, 0x2e, 0x2b, 0xbb, 0xb9, 0xb4, 0xbf, 0x4f, 0x63, 0xf9, 0x5e, + 0x8e, 0xcd, 0xa3, 0xfd, 0x11, 0x53, 0x2c, 0xf3, 0x54, 0x57, 0x3a, 0xf7, 0x88, 0x18, 0x57, 0x11, + 0x9d, 0x89, 0xb5, 0xff, 0xca, 0xb1, 0x66, 0x22, 0xbe, 0x24, 0x78, 0x13, 0x69, 0x47, 0x16, 0xa3, + 0xc0, 0x09, 0x6e, 0x9f, 0x0f, 0xed, 0x5f, 0x2f, 0xab, 0x9c, 0x95, 0xaa, 0x6f, 0xea, 0x00, 0xcf, + 0x55, 0xde, 0xbd, 0x8f, 0xc9, 0x18, 0xc7, 0xc1, 0x41, 0x9b, 0xb0, 0x72, 0x35, 0x1a, 0x0f, 0xb9, + 0x19, 0x11, 0x97, 0x85, 0x07, 0xa5, 0xe1, 0xc0, 0xb2, 0xb3, 0x0c, 0x74, 0x2f, 0x33, 0xb0, 0xc0, + 0xe9, 0xe2, 0x48, 0xe0, 0x42, 0xb0, 0x23, 0xe5, 0x58, 0x93, 0x60, 0x17, 0x30, 0xa8, 0x13, 0x2f, + 0x3c, 0x48, 0x18, 0x3b, 0xb1, 0x1b, 0x16, 0xc1, 0x57, 0xd6, 0xed, 0xd5, 0x75, 0x78, 0x01, 0xc1, + 0x23, 0x2f, 0x8e, 0xf3, 0x91, 0xcd, 0x72, 0x5e, 0x12, 0x53, 0x8d, 0xfa, 0xc0, 0x7d, 0xc5, 0x93, + 0x3f, 0xcf, 0xff, 0xf1, 0xf5, 0xc3, 0x23, 0xeb, 0x2e, 0x4b, 0x92, 0x9a, 0xe5, 0xb8, 0x2f, 0x6c, + 0xc3, 0x47, 0x07, 0xec, 0x83, 0x0d, 0xff, 0x9d, 0xa9, 0x4d, 0x09, 0x9d, 0x75, 0xb4, 0x24, 0x16, + 0xaf, 0x00, 0xe7, 0xc6, 0x1a, 0x8f, 0xaf, 0x64, 0x47, 0xa9, 0x50, 0xbf, 0x08, 0xfb, 0x2c, 0xf8, + 0x77, 0xff, 0x14, 0x6d, 0x58, 0xe0, 0x74, 0xb3, 0x02, 0x50, 0xa6, 0x0c, 0xaa, 0xb1, 0x0b, 0x2b, + 0x1d, 0x15, 0xa0, 0x4d, 0xfc, 0x88, 0xb7, 0x0f, 0x57, 0xf4, 0x92, 0xcc, 0x50, 0x3e, 0x35, 0x09, + 0x0a, 0x18, 0xab, 0xcc, 0xf8, 0x72, 0xfd, 0xf7, 0x9f, 0x1b, 0xdd, 0x5d, 0x2b, 0xcb, 0xcd, 0xbd, + 0xb7, 0x1d, 0x96, 0xdd, 0xb0, 0xd3, 0xdb, 0x64, 0xbe, 0x33, 0xa8, 0x92, 0x01, 0x0f, 0x2c, 0xe5, + 0x85, 0xd2, 0x79, 0x9d, 0x23, 0xfe, 0xf8, 0xda, 0x1d, 0x34, 0xd6, 0x95, 0x7f, 0x93, 0x4d, 0x7e, + 0xec, 0x4f, 0x03, 0xfe, 0xc1, 0x9d, 0xc0, 0x54, 0xe0, 0xca, 0xea, 0x95, 0x4d, 0x34, 0x82, 0x8c, + 0x94, 0xd7, 0x87, 0xd2, 0x24, 0x5a, 0x1f, 0x00, 0x9e, 0xd8, 0x85, 0xe3, 0xf3, 0xe0, 0x1c, 0xfb, + 0x9b, 0xf0, 0x63, 0xb8, 0x64, 0x45, 0x70, 0x32, 0x3d, 0x69, 0x9a, 0xf6, 0x72, 0x59, 0x1a, 0xb0, + 0x3a, 0xb5, 0xfb, 0x60, 0x38, 0x05, 0x0e, 0x52, 0x17, 0x3f, 0xfc, 0x09, 0x14, 0xee, 0x18, 0x04, + 0xe3, 0x76, 0x13, 0xad, 0x09, 0x5c, 0x2c, 0x2c, 0x5c, 0xed, 0x5d, 0xd6, 0xae, 0xe0, 0xfb, 0x2d, + 0x18, 0x47, 0x11, 0xb2, 0x15, 0x74, 0x67, 0x94, 0x05, 0x83, 0x9f, 0xe3, 0xd5, 0x53, 0x55, 0x9d, + 0x72, 0xbe, 0xf0, 0x2f, 0x69, 0x48, 0x6f, 0x63, 0xf9, 0x5e, 0xf2, 0xc0, 0xb0, 0xee, 0x6f, 0x66, + 0x51, 0x53, 0xbb, 0x4d, 0x28, 0xb6, 0x11, 0x36, 0x16, 0xc8, 0xea, 0xfd, 0x5e, 0xba, 0xda, 0x1e, + 0x76, 0x99, 0xd0, 0x65, 0x54, 0xea, 0xc0, 0x72, 0x6e, 0x77, 0xe7, 0xee, 0xdb, 0xd7, 0xfb, 0x9b, + 0x4d, 0xda, 0x37, 0x30, 0x6e, 0xf8, 0xc5, 0xef, 0x60, 0x13, 0x0e, 0x18, 0x58, 0x9c, 0x79, 0xda, + 0xd0, 0xc2, 0xa3, 0x2c, 0x66, 0xb2, 0x8b, 0xa3, 0x73, 0x3b, 0x35, 0x5a, 0xb1, 0x39, 0x09, 0x26, + 0x0d, 0xc0, 0x24, 0x89, 0xaf, 0x00, 0x53, 0xc4, 0xaa, 0x5c, 0x5e, 0xdd, 0x03, 0xb4, 0xf5, 0xfa, + 0x91, 0x66, 0xac, 0x1c, 0xad, 0x46, 0x38, 0x0e, 0x80, 0xa8, 0xba, 0x74, 0xf5, 0xf5, 0x60, 0xbf, + 0xfe, 0x7f, 0x0d, 0xb7, 0xad, 0x38, 0x3d, 0xb4, 0x23, 0xa3, 0xbc, 0x63, 0xc2, 0x08, 0xde, 0x9a, + 0x00, 0x88, 0xbf, 0x83, 0xe6, 0xec, 0xfc, 0x57, 0xf0, 0xff, 0xd5, 0xb9, 0x07, 0x3d, 0x42, 0x4d, + 0x82, 0xcb, 0xf9, 0x3d, 0xc8, 0x70, 0xea, 0xb5, 0x50, 0xb3, 0x11, 0x7c, 0x82, 0x3f, 0xfe, 0x57, + 0xf0, 0x0e, 0xf3, 0x8a, 0xbd, 0x9f, 0x93, 0xb4, 0x96, 0xe5, 0xf1, 0x97, 0x96, 0x0c, 0xd1, 0x79, + 0xdf, 0xf9, 0x6f, 0xc0, 0xd5, 0x54, 0x81, 0xbb, 0x97, 0x5f, 0xfb, 0xb6, 0x2e, 0x15, 0xb1, 0x7c, + 0x5b, 0x8b, 0x4b, 0xa3, 0x9a, 0x30, 0xf5, 0x82, 0x1a, 0x7d, 0x1c, 0x16, 0x6c, 0x08, 0xf2, 0x5c, + 0x17, 0x52, 0xbe, 0xff, 0x52, 0x3e, 0xf7, 0xbb, 0x25, 0x80, 0x5c, 0x46, 0x89, 0xe5, 0xc7, 0x62, + 0x74, 0xea, 0xdc, 0xa0, 0xda, 0xa0, 0x3f, 0x08, 0xdf, 0xcf, 0xaf, 0x9b, 0xdf, 0x78, 0x91, 0x6b, + 0x7c, 0x00, 0x77, 0x74, 0xb0, 0x3e, 0x0c, 0xd4, 0x6c, 0xaf, 0xdd, 0xed, 0xc7, 0x71, 0xa7, 0x16, + 0x3f, 0x33, 0x8f, 0x5a, 0xc7, 0xbd, 0x47, 0x76, 0x07, 0x24, 0x11, 0xd7, 0xa6, 0x9a, 0x56, 0x69, + 0x3b, 0x12, 0xb0, 0xe0, 0xd0, 0x12, 0x23, 0xc4, 0x8c, 0x32, 0xc4, 0x3a, 0x00, 0xba, 0x6e, 0xc3, + 0x89, 0x7c, 0x96, 0x48, 0x0a, 0x08, 0x24, 0x30, 0x8d, 0x2c, 0x0b, 0x56, 0xe6, 0xd5, 0x55, 0x39, + 0x5e, 0xf5, 0xed, 0x3c, 0xc0, 0x5b, 0x59, 0x0b, 0xe9, 0xbe, 0x2e, 0xb9, 0xb5, 0x7e, 0xff, 0xbd, + 0x6f, 0x80, 0x1e, 0xc4, 0xe4, 0x1b, 0xaf, 0xfc, 0x55, 0x1f, 0x19, 0x7d, 0x4c, 0x4f, 0xcc, 0xbc, + 0xee, 0xb2, 0x67, 0x02, 0xe9, 0x2f, 0x1b, 0xe4, 0xfa, 0x1b, 0xc5, 0xfc, 0x9a, 0x2e, 0x41, 0x82, + 0xc6, 0xfe, 0x01, 0x8f, 0x4b, 0x1b, 0x79, 0x1b, 0x2b, 0x85, 0xe9, 0x5f, 0x92, 0xe1, 0xba, 0x97, + 0xae, 0x9b, 0x68, 0x7c, 0xd2, 0x4c, 0x8d, 0xdc, 0xb2, 0xc0, 0x46, 0xd2, 0xe1, 0x5e, 0x6b, 0xf3, + 0x79, 0x7a, 0xf3, 0xcb, 0x3c, 0x5d, 0x23, 0xb0, 0x93, 0x47, 0x39, 0xfd, 0x4e, 0x1a, 0xe5, 0x95, + 0xc1, 0x47, 0x5f, 0xf9, 0xed, 0x0b, 0xdc, 0xcc, 0x75, 0x2a, 0x6a, 0x06, 0x00, 0x91, 0x19, 0x8c, + 0x56, 0xfd, 0x00, 0x93, 0x18, 0xfc, 0xd2, 0x30, 0xbd, 0xa2, 0xa2, 0xa6, 0xd8, 0x96, 0x78, 0xf1, + 0x8e, 0x7c, 0x46, 0xc8, 0x79, 0xf8, 0xda, 0xcb, 0x3b, 0x02, 0x6d, 0x5e, 0x01, 0x32, 0xf4, 0x86, + 0x7f, 0xe6, 0x7c, 0xfd, 0xcf, 0x08, 0x0f, 0x09, 0x6f, 0x03, 0xfa, 0x11, 0x96, 0x7b, 0x73, 0x75, + 0x90, 0xdc, 0xfe, 0x5e, 0x62, 0x61, 0x67, 0x1b, 0xc8, 0x8c, 0xb9, 0x32, 0x98, 0x4d, 0x9a, 0xd4, + 0xa0, 0x39, 0x5b, 0xd6, 0x0b, 0x56, 0x56, 0xcf, 0x55, 0x43, 0x2f, 0x6f, 0xb8, 0xf6, 0x83, 0xff, + 0xe7, 0x62, 0x7b, 0x73, 0xf9, 0x4e, 0xea, 0xec, 0x3e, 0x1e, 0xa6, 0xbf, 0xbf, 0x87, 0xdb, 0x1a, + 0xdb, 0xca, 0xb4, 0xc7, 0xa5, 0x30, 0x2b, 0xcd, 0x79, 0xef, 0x9b, 0x58, 0xdc, 0x30, 0xf8, 0x70, + 0x8e, 0x2f, 0x42, 0xde, 0x83, 0x44, 0x25, 0xf7, 0xdd, 0x55, 0x3e, 0x12, 0x55, 0x4e, 0x15, 0x5f, + 0x37, 0x91, 0xe9, 0xe3, 0x1f, 0x20, 0xda, 0x51, 0xcb, 0xff, 0xfd, 0x6d, 0x72, 0xfc, 0x3f, 0x5b, + 0x0f, 0xc7, 0x70, 0xcd, 0x84, 0xd9, 0x8e, 0x36, 0x92, 0x1d, 0x4a, 0x61, 0x70, 0x9b, 0x9b, 0x68, + 0x9b, 0x27, 0xc6, 0x99, 0x10, 0xa3, 0xce, 0x53, 0x21, 0xf4, 0x31, 0x3c, 0x87, 0x84, 0x69, 0x81, + 0x9d, 0xa5, 0xef, 0x0f, 0xf1, 0xca, 0xae, 0xf0, 0x63, 0x1c, 0x84, 0x5b, 0x8f, 0x77, 0xe5, 0x6b, + 0x11, 0xec, 0x26, 0x59, 0x9f, 0x16, 0x89, 0xe1, 0xc2, 0xad, 0x56, 0xb8, 0x2e, 0x31, 0x68, 0x5d, + 0x2a, 0x83, 0xf6, 0xef, 0xfe, 0x61, 0x76, 0x9d, 0x60, 0x2a, 0x87, 0xc5, 0x85, 0xff, 0x22, 0xe4, + 0x4b, 0x08, 0x48, 0x13, 0x8a, 0x00, 0x3b, 0x48, 0xc9, 0x21, 0x06, 0xc1, 0xbb, 0xd9, 0xd1, 0x3d, + 0x52, 0x49, 0x6a, 0x44, 0x93, 0x09, 0xc8, 0x8b, 0x8a, 0x5e, 0x7e, 0x8d, 0x0b, 0x26, 0xc3, 0x59, + 0xbb, 0xad, 0x92, 0xaa, 0x46, 0x0b, 0xbe, 0xd6, 0xaa, 0xa0, 0x36, 0x5f, 0x34, 0x6a, 0x88, 0x84, + 0x0a, 0xfc, 0xa8, 0xb8, 0xbd, 0xb4, 0xa4, 0xf4, 0xc6, 0xe1, 0x88, 0x55, 0x5d, 0x4e, 0xc9, 0xdc, + 0x7d, 0xa2, 0x71, 0x0d, 0x18, 0x1e, 0x16, 0x1d, 0x17, 0x11, 0xb3, 0x91, 0x92, 0xf0, 0xc6, 0x1b, + 0x3a, 0x54, 0x80, 0x8f, 0x11, 0x71, 0x9b, 0x02, 0x16, 0xf4, 0x8f, 0xf0, 0x37, 0xf2, 0xea, 0xa8, + 0x95, 0x61, 0x8e, 0x8f, 0x22, 0x22, 0xc0, 0xef, 0x51, 0x0b, 0x18, 0x8d, 0xd8, 0xe8, 0x0b, 0x76, + 0x3e, 0xa5, 0x28, 0xc3, 0x6f, 0xde, 0xf1, 0xf4, 0x55, 0x7d, 0x48, 0xfd, 0xe3, 0xa5, 0x64, 0x45, + 0x70, 0x3c, 0xbe, 0x0f, 0x06, 0x22, 0xc8, 0x95, 0x89, 0x0a, 0x56, 0xd6, 0x8a, 0x36, 0x42, 0xbe, + 0xe6, 0x20, 0x00, 0x5c, 0xaa, 0xc6, 0x2e, 0xea, 0x19, 0x11, 0xfc, 0x07, 0x38, 0x08, 0x30, 0x7f, + 0xb1, 0xe7, 0xfd, 0x71, 0x1c, 0x37, 0xc1, 0xd1, 0x4c, 0x3f, 0x16, 0x4c, 0x4d, 0x06, 0x69, 0xca, + 0x3b, 0x8b, 0x79, 0xfd, 0x05, 0x0c, 0x8c, 0x60, 0xee, 0xaa, 0x91, 0x16, 0xf1, 0xab, 0x8a, 0x55, + 0xf1, 0xb2, 0x85, 0xfc, 0xa7, 0xde, 0x6f, 0x32, 0xd3, 0x1f, 0xfd, 0xc2, 0x0d, 0xc7, 0x74, 0x4d, + 0xcb, 0xae, 0x77, 0x96, 0xd3, 0xf3, 0x0a, 0xa0, 0x97, 0xd1, 0xf5, 0x54, 0xb8, 0xba, 0x45, 0x63, + 0x1f, 0xe4, 0xef, 0x80, 0x63, 0xd4, 0xef, 0xef, 0x74, 0x56, 0x93, 0xf3, 0xfe, 0x60, 0x97, 0xcb, + 0xf5, 0x55, 0x89, 0x0b, 0x92, 0x4e, 0xb5, 0x22, 0xe3, 0x0e, 0xc4, 0x5e, 0xc9, 0x0b, 0xc8, 0x52, + 0xc1, 0x43, 0x0a, 0x6e, 0x6e, 0xee, 0xe9, 0xd2, 0x58, 0xe1, 0x08, 0xd5, 0x93, 0xdd, 0x96, 0x57, + 0x8b, 0x06, 0x2f, 0xb4, 0x3b, 0x0a, 0x00, 0x5f, 0x9d, 0x55, 0x14, 0x57, 0xfe, 0xee, 0xbe, 0x19, + 0x61, 0xa5, 0x63, 0x81, 0x6a, 0xf0, 0x1e, 0xf8, 0xdc, 0xa0, 0xf4, 0x98, 0xaa, 0x0b, 0x40, 0xfa, + 0xea, 0xb1, 0xb7, 0xce, 0xb7, 0x80, 0x3b, 0x56, 0x09, 0xb6, 0xa5, 0x92, 0x84, 0x0b, 0x01, 0xca, + 0x51, 0x86, 0xad, 0xd5, 0x1a, 0xc5, 0x9f, 0xba, 0x2b, 0xc5, 0x30, 0x66, 0xd2, 0xbc, 0xa5, 0xb1, + 0xcd, 0x3a, 0x73, 0x29, 0x49, 0x47, 0x58, 0xe3, 0xb7, 0x55, 0xeb, 0x15, 0x7f, 0x0d, 0xf8, 0x12, + 0x55, 0x02, 0x92, 0x3a, 0xe7, 0x60, 0xdf, 0xfa, 0x3a, 0x3a, 0x01, 0x35, 0xf0, 0x06, 0xe5, 0x85, + 0xdd, 0x21, 0xef, 0xe7, 0x84, 0xda, 0x08, 0x22, 0xa2, 0xab, 0x83, 0x92, 0x25, 0x15, 0x8b, 0x00, + 0x4f, 0x6a, 0xc9, 0x01, 0xa4, 0xc1, 0x62, 0xf6, 0x07, 0x24, 0x19, 0xbb, 0x5d, 0xe0, 0x36, 0x69, + 0xf5, 0x6a, 0xef, 0x22, 0x73, 0xc7, 0xb6, 0xc2, 0x03, 0xc9, 0x53, 0x9f, 0x4d, 0xd5, 0x1e, 0x03, + 0x00, 0xd9, 0xa4, 0x44, 0x44, 0x45, 0x15, 0x2e, 0xdb, 0x91, 0x09, 0xf0, 0xd8, 0x30, 0x0b, 0x08, + 0x29, 0x55, 0x1e, 0xd2, 0x58, 0xa1, 0xed, 0xda, 0xe0, 0x64, 0xec, 0x83, 0x2b, 0x5b, 0x30, 0x7a, + 0x90, 0xb0, 0x5f, 0x1a, 0x55, 0x38, 0xa6, 0x5d, 0x6f, 0xaa, 0x4e, 0x03, 0x6e, 0x00, 0x0b, 0xc6, + 0x35, 0x5d, 0x37, 0x23, 0xea, 0xa9, 0x81, 0xa2, 0x52, 0x0e, 0x9c, 0xd5, 0x7e, 0xaa, 0x98, 0x05, + 0x5c, 0x0c, 0xf3, 0x41, 0x07, 0x3e, 0x81, 0x6b, 0xf1, 0x7f, 0x50, 0xef, 0x9b, 0x8f, 0xc2, 0xf0, + 0x97, 0xaf, 0xbf, 0x00, 0x52, 0x02, 0x57, 0x59, 0xfc, 0x75, 0x0c, 0x90, 0x1a, 0x47, 0x17, 0x0e, + 0xf0, 0x0d, 0x3f, 0x6e, 0x97, 0x80, 0x99, 0x60, 0x37, 0x6a, 0xcc, 0xe9, 0xd9, 0x11, 0x09, 0xc4, + 0x09, 0x60, 0x00, 0x25, 0x06, 0x2f, 0xb6, 0x8e, 0xaf, 0x68, 0xa9, 0x29, 0xab, 0xa9, 0xaa, 0xa7, + 0x45, 0xcb, 0x0c, 0xe4, 0x85, 0xbc, 0x27, 0xd5, 0xa6, 0x00, 0x72, 0xa5, 0xc0, 0x72, 0xf1, 0xef, + 0x71, 0x42, 0x0c, 0x1b, 0x02, 0xdb, 0x44, 0x40, 0x0b, 0xde, 0x15, 0x7c, 0x43, 0xdf, 0xe2, 0x52, + 0x0b, 0x2c, 0x9f, 0xf5, 0x24, 0xab, 0xc4, 0x69, 0xaa, 0xa2, 0xe0, 0x39, 0xe0, 0x2e, 0x11, 0x2e, + 0x11, 0x39, 0x0f, 0x38, 0x5e, 0x6f, 0x2a, 0x70, 0xa3, 0x5e, 0x9c, 0xdc, 0xe8, 0xd5, 0x63, 0x6b, + 0x70, 0x05, 0xf0, 0x27, 0xbd, 0x70, 0x0d, 0x4b, 0xf5, 0x54, 0xe0, 0x79, 0x11, 0x4a, 0x16, 0xb9, + 0x03, 0x2f, 0x58, 0xf5, 0x39, 0xbc, 0xa9, 0xc9, 0xff, 0xc5, 0x94, 0x6a, 0xa8, 0x51, 0x05, 0x0b, + 0x82, 0xfe, 0xa0, 0xd7, 0xd5, 0xc4, 0xae, 0xb7, 0x45, 0xfc, 0x4a, 0x2f, 0x07, 0x72, 0x87, 0x70, + 0x1c, 0x88, 0x97, 0x8d, 0x59, 0x30, 0x3d, 0xfa, 0x80, 0xd3, 0xd8, 0x09, 0xaa, 0x8f, 0xbc, 0x03, + 0xfc, 0x0a, 0xaf, 0x00, 0x70, 0x16, 0xa4, 0xec, 0xaa, 0xa3, 0xe3, 0x76, 0xd2, 0x85, 0x11, 0xef, + 0x91, 0x91, 0x1c, 0xc0, 0x67, 0xbc, 0x50, 0xf8, 0x67, 0xbc, 0x32, 0x78, 0xf5, 0xde, 0xed, 0x27, + 0x67, 0x01, 0x12, 0xf0, 0x46, 0x7d, 0x49, 0xe3, 0x75, 0xfc, 0x3a, 0x54, 0xe2, 0xf0, 0xe6, 0x71, + 0xbf, 0x55, 0x14, 0xaa, 0x28, 0xe0, 0xec, 0x65, 0x5e, 0x7f, 0x16, 0x44, 0x97, 0x02, 0x92, 0x13, + 0x34, 0x9f, 0x4b, 0xc4, 0xd3, 0x78, 0xe5, 0x6b, 0x99, 0x29, 0xa3, 0xa4, 0xad, 0xd1, 0xe5, 0x39, + 0xc9, 0x07, 0x00, 0x24, 0x65, 0x03, 0x6b, 0x27, 0xb7, 0xb1, 0x48, 0xbf, 0x56, 0x0e, 0xe6, 0xa3, + 0xb9, 0x46, 0x42, 0x18, 0xe8, 0xfa, 0x07, 0x80, 0x87, 0xee, 0xc8, 0x88, 0xc0, 0x21, 0x89, 0x8f, + 0x58, 0xe9, 0x08, 0xb4, 0x6b, 0xbc, 0x79, 0xac, 0x56, 0x5f, 0xa6, 0x4b, 0x89, 0x6f, 0x88, 0xe2, + 0xc0, 0x69, 0x93, 0x51, 0x1f, 0x63, 0xda, 0xaa, 0xe7, 0x8b, 0xfe, 0xf4, 0xf6, 0x92, 0x34, 0xb5, + 0x55, 0x0a, 0x41, 0x5d, 0x8c, 0x48, 0x2d, 0x22, 0x64, 0x44, 0xf0, 0x22, 0xc0, 0x77, 0x44, 0x67, + 0xb4, 0xc4, 0x2b, 0x97, 0x85, 0xe0, 0x4b, 0xe1, 0x77, 0x0e, 0x83, 0x04, 0x74, 0xe8, 0x6d, 0x4c, + 0xf0, 0xcb, 0x84, 0xe9, 0x60, 0x4b, 0x21, 0x6a, 0x73, 0xa6, 0x49, 0x1d, 0x2c, 0x22, 0x72, 0xae, + 0xd7, 0xb8, 0xc5, 0xf8, 0x22, 0x00, 0x76, 0xf2, 0x85, 0x7c, 0x8e, 0x30, 0x92, 0x1f, 0x02, 0xe1, + 0x12, 0x83, 0xc5, 0x80, 0xc1, 0xc4, 0x0d, 0x53, 0x5b, 0xd8, 0xe8, 0x95, 0x4d, 0x24, 0x81, 0xc0, + 0x2a, 0x34, 0x24, 0x6a, 0x47, 0x7f, 0xf3, 0x8c, 0x15, 0x6d, 0xda, 0xf8, 0x1d, 0xe1, 0x6d, 0x1c, + 0x00, 0xb8, 0x4c, 0x74, 0xfa, 0xd1, 0xaa, 0x35, 0xce, 0xba, 0x51, 0xa8, 0xc3, 0x87, 0x22, 0x10, + 0x21, 0xf2, 0xbc, 0xbc, 0xa6, 0x8d, 0x6f, 0x6d, 0x74, 0x66, 0xb8, 0x64, 0xe6, 0x7f, 0xe3, 0x50, + 0xef, 0x59, 0x02, 0x31, 0xeb, 0x0c, 0x69, 0x2a, 0x5a, 0xe3, 0x73, 0xe4, 0x8d, 0xfe, 0xcd, 0xab, + 0x00, 0x4b, 0x46, 0x06, 0x0d, 0xbf, 0x57, 0xb2, 0xe2, 0x4d, 0x69, 0xb0, 0xda, 0x69, 0x22, 0x17, + 0xde, 0x65, 0xdf, 0xe4, 0x3b, 0x98, 0x18, 0x6b, 0xda, 0xd3, 0x25, 0xbd, 0x36, 0x1c, 0xa5, 0x56, + 0x8e, 0x2c, 0x60, 0x17, 0x38, 0x70, 0x3b, 0xf6, 0x64, 0x44, 0x5e, 0x34, 0xd8, 0xe3, 0x6d, 0x74, + 0xd8, 0xcd, 0xe1, 0x16, 0x31, 0xc2, 0x5c, 0x2d, 0xb6, 0x34, 0xb7, 0x95, 0x6e, 0x8e, 0x2e, 0x31, + 0x02, 0xac, 0x20, 0x9e, 0x05, 0x45, 0x86, 0x01, 0x10, 0x54, 0xe3, 0x91, 0xad, 0xf0, 0x9e, 0x3d, + 0x96, 0xda, 0x7d, 0xe1, 0x25, 0x06, 0x45, 0x00, 0x48, 0xe4, 0xcd, 0xe0, 0xec, 0x06, 0x4e, 0x2d, + 0x92, 0x74, 0xa1, 0x26, 0x5e, 0xe9, 0x26, 0x8a, 0x30, 0x21, 0x34, 0x47, 0x44, 0x67, 0xea, 0xd6, + 0x59, 0xa4, 0x39, 0xb5, 0x87, 0x22, 0x3f, 0x63, 0x52, 0xe3, 0xda, 0xbc, 0x80, 0x01, 0x45, 0x17, + 0xdd, 0x32, 0xcd, 0x89, 0x8c, 0x22, 0xc2, 0xc8, 0x16, 0xa8, 0x44, 0x61, 0x18, 0x56, 0x15, 0x99, + 0x66, 0x46, 0x64, 0x61, 0x18, 0x56, 0x22, 0xd5, 0xce, 0x8c, 0xe7, 0x0a, 0xcc, 0x8e, 0xba, 0xf2, + 0x2d, 0x91, 0xd7, 0xd6, 0x37, 0xc7, 0xbb, 0xe1, 0xc9, 0x3d, 0xf1, 0xbf, 0x93, 0x8f, 0x28, 0xdb, + 0xf4, 0x28, 0x32, 0x0e, 0xa2, 0xb3, 0x35, 0x5f, 0x02, 0x4e, 0xda, 0xb0, 0xf7, 0xe8, 0xcb, 0x50, + 0x9a, 0xf6, 0xe7, 0x1a, 0xe3, 0x80, 0x4f, 0x29, 0xb5, 0x04, 0xfd, 0x8a, 0x95, 0x8c, 0x09, 0xa4, + 0x0e, 0x58, 0x13, 0x47, 0xa8, 0x13, 0x34, 0xec, 0xf6, 0x7f, 0x2b, 0x30, 0xf2, 0xe2, 0xca, 0xcd, + 0x44, 0xff, 0x75, 0xeb, 0xfd, 0xa7, 0x8f, 0x6e, 0x6f, 0xf4, 0x6e, 0x7c, 0x0f, 0x75, 0x5b, 0x9f, + 0x97, 0x5d, 0xc6, 0x38, 0xaa, 0xe3, 0x2d, 0x5a, 0x22, 0x8f, 0xcc, 0xb0, 0x82, 0xcc, 0xa6, 0xbd, + 0x00, 0xe6, 0xcf, 0x2a, 0x50, 0xd0, 0x0e, 0xca, 0x4d, 0xb8, 0x27, 0xec, 0x86, 0x8c, 0xe4, 0xaa, + 0xc1, 0xec, 0xd9, 0x7e, 0x87, 0x4d, 0x51, 0xa3, 0x1d, 0x94, 0x88, 0xf0, 0x30, 0xac, 0x20, 0xfb, + 0xbe, 0x76, 0xf2, 0xbb, 0x1f, 0xca, 0x0e, 0x83, 0x13, 0x78, 0x6c, 0xf0, 0x97, 0x4a, 0x62, 0x53, + 0xd0, 0x21, 0xa5, 0xc8, 0x5f, 0x3b, 0xe0, 0x1a, 0x17, 0xdf, 0x9b, 0x01, 0x68, 0x1b, 0x6a, 0xcf, + 0xa6, 0x41, 0xe3, 0x46, 0x70, 0x1c, 0xfb, 0x3c, 0xb0, 0x1c, 0x3b, 0xe5, 0x7c, 0xf4, 0x21, 0x77, + 0x62, 0x2c, 0x2a, 0x5d, 0xee, 0xd2, 0xec, 0x70, 0x88, 0x57, 0x11, 0xc8, 0x42, 0xf8, 0x67, 0x8e, + 0xc8, 0x8c, 0xd4, 0x1e, 0xce, 0x0d, 0xdb, 0x03, 0xf4, 0x73, 0x43, 0x05, 0xbf, 0xad, 0xa1, 0x4c, + 0x6e, 0xa4, 0x55, 0x00, 0x0b, 0x5d, 0x07, 0xbc, 0x19, 0x6f, 0x3d, 0x4f, 0x80, 0xe4, 0x53, 0x7f, + 0xdf, 0xd5, 0xc2, 0x0f, 0xc5, 0xde, 0xfa, 0x78, 0xf5, 0xd6, 0xfb, 0xda, 0xaa, 0xba, 0xae, 0xfe, + 0xf3, 0xce, 0x8a, 0xa4, 0x41, 0x0b, 0xe3, 0xc7, 0x01, 0x1b, 0x35, 0xdb, 0x83, 0xfc, 0x91, 0x4d, + 0xf2, 0xe7, 0x76, 0x0f, 0xc0, 0xaf, 0x68, 0xe1, 0x81, 0x18, 0xb3, 0xf0, 0x02, 0x8d, 0xcd, 0x36, + 0x01, 0x4a, 0x94, 0xe2, 0x7e, 0xaa, 0xc2, 0x91, 0x44, 0xa5, 0xd5, 0x50, 0x45, 0x87, 0xf5, 0xe4, + 0x3b, 0xc0, 0xd9, 0x81, 0x1b, 0x24, 0x29, 0x98, 0xd4, 0x9e, 0x7f, 0xe7, 0xea, 0xa9, 0x83, 0xab, + 0xf7, 0xb4, 0x88, 0x98, 0x87, 0x9c, 0xf6, 0xb7, 0x35, 0xf8, 0xd8, 0x35, 0x4b, 0xfb, 0xde, 0xd1, + 0x81, 0xc6, 0x6e, 0x0e, 0x9c, 0x76, 0x8e, 0x03, 0x95, 0x27, 0x74, 0x10, 0xc2, 0x9f, 0x82, 0x8f, + 0x4d, 0x75, 0x78, 0xf3, 0x91, 0xeb, 0x8e, 0x1d, 0x91, 0xfd, 0x80, 0x80, 0x39, 0xc9, 0xb1, 0x07, + 0xbe, 0x03, 0x63, 0x65, 0x58, 0x5f, 0x74, 0x2e, 0x4d, 0x35, 0xe6, 0xb4, 0x55, 0x5c, 0x62, 0xe7, + 0x3e, 0x71, 0x55, 0x5a, 0x03, 0xe7, 0x6d, 0xf3, 0x6b, 0xd9, 0x0e, 0x56, 0x88, 0xe6, 0x35, 0x50, + 0x09, 0x52, 0xdf, 0x41, 0xad, 0xbb, 0xad, 0x9f, 0xf4, 0xc1, 0x07, 0xfa, 0x84, 0x0f, 0x9e, 0x5e, + 0xa0, 0xe3, 0x3c, 0xa5, 0x6e, 0x02, 0x6f, 0x11, 0xc1, 0x35, 0xc8, 0xc1, 0xce, 0xe2, 0x5f, 0x3c, + 0x13, 0x58, 0xac, 0xa9, 0xc5, 0xf9, 0xc3, 0xb4, 0x8e, 0xcf, 0x71, 0xf4, 0x38, 0x0e, 0xeb, 0x1e, + 0xe8, 0x21, 0x39, 0xad, 0x9a, 0xe5, 0x36, 0x68, 0x9d, 0xe0, 0xac, 0x76, 0x1c, 0x3b, 0xa5, 0x8d, + 0xfe, 0xb8, 0x84, 0xf7, 0xf6, 0xd2, 0xc3, 0x89, 0x0c, 0xb2, 0x1c, 0xcb, 0x9a, 0x40, 0x83, 0x98, + 0x97, 0xe4, 0xfa, 0xdb, 0x77, 0x39, 0x17, 0x69, 0xc1, 0x9d, 0x4d, 0xaa, 0x54, 0x7f, 0xff, 0xd5, + 0xe9, 0x4a, 0x1c, 0x92, 0x84, 0x4d, 0x41, 0x0b, 0xb3, 0xc7, 0x19, 0x6f, 0xa5, 0x92, 0xe3, 0x46, + 0x15, 0x8a, 0x6c, 0xdd, 0x66, 0x08, 0xbc, 0x68, 0xa7, 0x80, 0xea, 0xf3, 0xec, 0x70, 0x9c, 0x8f, + 0x6f, 0x89, 0x5f, 0x05, 0x42, 0xd2, 0xfb, 0xfb, 0x0b, 0x3b, 0xf3, 0x3b, 0xb2, 0xdc, 0x8f, 0xb1, + 0x6e, 0x1f, 0xe8, 0x89, 0xef, 0xc2, 0xfe, 0x02, 0x85, 0x3e, 0x72, 0x4b, 0x21, 0x34, 0x01, 0xce, + 0x63, 0xc7, 0x04, 0x6d, 0x33, 0xbc, 0x7c, 0xa7, 0x55, 0x7a, 0x0f, 0x0f, 0x45, 0x04, 0xf3, 0xa5, + 0xe5, 0xc0, 0xfb, 0x2e, 0xb1, 0xd9, 0x73, 0x1f, 0x7f, 0x96, 0x40, 0xe3, 0xc0, 0x76, 0xf6, 0xaf, + 0x2b, 0x2b, 0x0a, 0x7b, 0x7f, 0xde, 0x05, 0xbd, 0xbf, 0x7e, 0x17, 0x23, 0x25, 0x2e, 0x62, 0x5b, + 0xf0, 0x27, 0x55, 0x4e, 0xc5, 0x7d, 0x25, 0xf2, 0x9a, 0x3a, 0xe7, 0x14, 0xe3, 0x80, 0xea, 0x31, + 0x0b, 0x80, 0x8b, 0xc0, 0x05, 0x5d, 0xdd, 0x1e, 0xe3, 0x93, 0x6b, 0xcb, 0x79, 0x7b, 0x3f, 0xb5, + 0x06, 0x52, 0x2b, 0xe6, 0x3a, 0xaa, 0xf1, 0xaf, 0x1d, 0x96, 0x6b, 0x0e, 0x52, 0x04, 0xd7, 0x7e, + 0x69, 0xf7, 0x2b, 0x58, 0x69, 0xb6, 0xf5, 0x50, 0xbc, 0x7a, 0x85, 0x79, 0x42, 0x9c, 0x95, 0x5e, + 0x60, 0x5c, 0x72, 0x1b, 0xe4, 0xaa, 0xb6, 0xf1, 0xe6, 0x77, 0xec, 0xb4, 0x5f, 0x6c, 0xd1, 0xe9, + 0x28, 0xb0, 0x25, 0x40, 0xc2, 0x5e, 0xbe, 0x73, 0x06, 0xcb, 0xd2, 0x3c, 0xd0, 0x71, 0x75, 0x20, + 0xb9, 0xaa, 0x90, 0x1d, 0xc8, 0x88, 0xe0, 0x4a, 0xf0, 0x48, 0x07, 0x8a, 0x1c, 0xcc, 0x41, 0x92, + 0x27, 0xd9, 0xa9, 0xd0, 0x2f, 0x78, 0x64, 0x6d, 0x79, 0x5f, 0x06, 0xbf, 0xa1, 0xf6, 0xe3, 0xc1, + 0xf7, 0xd6, 0xb8, 0x69, 0xac, 0x1e, 0xdf, 0x9f, 0x17, 0x00, 0xd1, 0xb9, 0x63, 0xaf, 0x46, 0x7f, + 0x2e, 0x44, 0x7e, 0x01, 0xf6, 0x96, 0x34, 0x75, 0xdd, 0x01, 0xf7, 0xc0, 0x2f, 0x80, 0x15, 0x14, + 0x1c, 0xe4, 0x32, 0x8e, 0x01, 0xce, 0x20, 0xc7, 0x5f, 0x22, 0x3e, 0x02, 0xd9, 0x6f, 0x01, 0x6c, + 0x18, 0x4f, 0x5a, 0xfe, 0x6d, 0x55, 0xa1, 0xce, 0xc3, 0x82, 0x48, 0x31, 0xe4, 0x44, 0xe0, 0x28, + 0x3c, 0x19, 0xc0, 0x39, 0x91, 0x83, 0xc0, 0xe5, 0x81, 0x77, 0xa8, 0x1f, 0xf6, 0xea, 0x2f, 0xb4, + 0xd1, 0xc6, 0x38, 0x9c, 0x1c, 0x07, 0x88, 0x83, 0x07, 0x4c, 0xc6, 0x9e, 0xe3, 0x5e, 0x08, 0xb1, + 0x98, 0xf8, 0x3e, 0xaa, 0xc9, 0x6e, 0xc3, 0x9e, 0x1c, 0x61, 0x99, 0x11, 0xdc, 0x01, 0x1c, 0x07, + 0xf6, 0x97, 0x18, 0xb5, 0x13, 0xa2, 0xe9, 0x58, 0xef, 0xfb, 0xe2, 0xe5, 0x97, 0xff, 0xea, 0xa9, + 0x34, 0xca, 0xcc, 0x2f, 0x7f, 0xb1, 0x1c, 0xe0, 0x05, 0x70, 0x64, 0x44, 0xf0, 0x0c, 0xe0, 0x19, + 0xc0, 0x63, 0x2a, 0x10, 0x9c, 0x44, 0xdc, 0x6b, 0xaa, 0x57, 0xbc, 0xef, 0x78, 0x00, 0x39, 0xc4, + 0xc7, 0x5e, 0x8a, 0x81, 0x8c, 0x88, 0xde, 0x00, 0x8c, 0x06, 0xbe, 0xc3, 0xc9, 0xae, 0x51, 0x60, + 0xd3, 0x8a, 0x78, 0xc0, 0x0e, 0x25, 0xff, 0xfd, 0xdb, 0x8f, 0x09, 0xd7, 0x52, 0xac, 0x4d, 0xd5, + 0x5c, 0xa9, 0xe3, 0xad, 0xc1, 0x80, 0x60, 0x39, 0xc3, 0x87, 0x70, 0xe1, 0x52, 0x38, 0x15, 0xc0, + 0x70, 0xe2, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, + 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, + 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, + 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, + 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, + 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, + 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, + 0x87, 0x0e, 0x05, 0x23, 0x0f, 0xfe, 0xf6, 0xa7, 0xbf, 0x78, 0xfb, 0x2d, 0xfb, 0xc5, 0x00, 0x00, + 0x00, 0x00, 0x45, 0x49, 0x44, 0x4e, 0x42, 0xae, 0x82, 0x60, + ], + disp: function() + { + // Do Nothing + + throw "Does Nothing!"; + } + +}; + +try +{ + LOGO.disp(); +} +catch(e) +{ + alert("Error: " + e + "\n"); +} + diff --git a/jetty-servlets/src/test/resources/big_script.js.sha1 b/jetty-servlets/src/test/resources/big_script.js.sha1 new file mode 100644 index 00000000000..5ac71c8b8b3 --- /dev/null +++ b/jetty-servlets/src/test/resources/big_script.js.sha1 @@ -0,0 +1 @@ +77634b336741a98d2ef79484281245b12e9db939 big_script.js diff --git a/jetty-servlets/src/test/resources/small_script.js b/jetty-servlets/src/test/resources/small_script.js new file mode 100644 index 00000000000..207de2be410 --- /dev/null +++ b/jetty-servlets/src/test/resources/small_script.js @@ -0,0 +1,29 @@ +//---------------------------------------------------------------------- +// +// Silly / Pointless Javascript to test GZIP compression. +// +//---------------------------------------------------------------------- + +var LOGO = { + dat: [ + 0x50, 0x89, 0x47, 0x4e, 0x0a, 0x0d, 0x0a, 0x1a, 0x00, 0x00, 0x0d, 0x00, 0x48, 0x49, 0x52, 0x44, + 0x00, 0x00, 0x45, 0x49, 0x44, 0x4e, 0x42, 0xae, 0x82, 0x60, + ], + disp: function() + { + // Do Nothing + + throw "Does Nothing!"; + } + +}; + +try +{ + LOGO.disp(); +} +catch(e) +{ + alert("Error: " + e + "\n"); +} + diff --git a/jetty-servlets/src/test/resources/small_script.js.sha1 b/jetty-servlets/src/test/resources/small_script.js.sha1 new file mode 100644 index 00000000000..ca4c476094a --- /dev/null +++ b/jetty-servlets/src/test/resources/small_script.js.sha1 @@ -0,0 +1 @@ +b8455ea37194b938cfc1773a73ecee2c994fa98e small_script.js diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml index 83385dd5b8d..9fcfa2c2cc8 100644 --- a/jetty-start/pom.xml +++ b/jetty-start/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-start diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java index 0f49e0b28b8..3668254783e 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java @@ -23,36 +23,39 @@ import java.net.URLClassLoader; import java.util.StringTokenizer; import java.util.Vector; - /** * Class to handle CLASSPATH construction */ -public class Classpath { +public class Classpath +{ - private final Vector _elements = new Vector(); + private final Vector _elements = new Vector(); public Classpath() - {} + { + } public Classpath(String initial) { addClasspath(initial); } - + public File[] getElements() { return _elements.toArray(new File[_elements.size()]); } - + public int count() { return _elements.size(); } - + public boolean addComponent(String component) { - if ((component != null)&&(component.length()>0)) { - try { + if ((component != null) && (component.length() > 0)) + { + try + { File f = new File(component); if (f.exists()) { @@ -63,36 +66,46 @@ public class Classpath { return true; } } - } catch (IOException e) {} + } + catch (IOException e) + { + } } return false; } - + public boolean addComponent(File component) { - if (component != null) { - try { - if (component.exists()) { + if (component != null) + { + try + { + if (component.exists()) + { File key = component.getCanonicalFile(); - if (!_elements.contains(key)) { + if (!_elements.contains(key)) + { _elements.add(key); return true; } } - } catch (IOException e) {} + } + catch (IOException e) + { + } } return false; } public boolean addClasspath(String s) { - boolean added=false; + boolean added = false; if (s != null) { StringTokenizer t = new StringTokenizer(s, File.pathSeparator); while (t.hasMoreTokens()) { - added|=addComponent(t.nextToken()); + added |= addComponent(t.nextToken()); } } return added; @@ -103,40 +116,49 @@ public class Classpath { int i = 0; for (File element : _elements) { - out.printf("%2d: %s\n",i++,element.getAbsolutePath()); + out.printf("%2d: %s\n", i++, element.getAbsolutePath()); } } - + @Override public String toString() { StringBuffer cp = new StringBuffer(1024); int cnt = _elements.size(); - if (cnt >= 1) { - cp.append( ((_elements.elementAt(0))).getPath() ); + if (cnt >= 1) + { + cp.append(((_elements.elementAt(0))).getPath()); } - for (int i=1; i < cnt; i++) { + for (int i = 1; i < cnt; i++) + { cp.append(File.pathSeparatorChar); - cp.append( ((_elements.elementAt(i))).getPath() ); + cp.append(((_elements.elementAt(i))).getPath()); } return cp.toString(); } - - public ClassLoader getClassLoader() { + + public ClassLoader getClassLoader() + { int cnt = _elements.size(); URL[] urls = new URL[cnt]; - for (int i=0; i < cnt; i++) { - try { - String u=_elements.elementAt(i).toURI().toURL().toString(); - urls[i] = new URL(encodeFileURL(u)); - } catch (MalformedURLException e) {} + for (int i = 0; i < cnt; i++) + { + try + { + urls[i] = _elements.elementAt(i).toURI().toURL(); + } + catch (MalformedURLException e) + { + } } - + ClassLoader parent = Thread.currentThread().getContextClassLoader(); - if (parent == null) { + if (parent == null) + { parent = Classpath.class.getClassLoader(); } - if (parent == null) { + if (parent == null) + { parent = ClassLoader.getSystemClassLoader(); } return new Loader(urls, parent); @@ -152,71 +174,17 @@ public class Classpath { @Override public String toString() { - return "startJarLoader@"+Long.toHexString(hashCode()); + return "startJarLoader@" + Long.toHexString(hashCode()); } } - - public static String encodeFileURL(String path) - { - byte[] bytes; - try - { - bytes=path.getBytes("utf-8"); - } - catch (UnsupportedEncodingException e) - { - bytes=path.getBytes(); - } - - StringBuffer buf = new StringBuffer(bytes.length*2); - buf.append("file:"); - - synchronized(buf) - { - for (int i=5;i='a' && b<='z' || b>='A' && b<='Z' || b>='0' && b<='9') - { - buf.append((char)b); - continue; - } - } - buf.append('%'); - buf.append(Integer.toHexString((0xf0&b)>>4)); - buf.append(Integer.toHexString((0x0f&b))); - continue; - } - } - } - return buf.toString(); - } + /** - * Overlay another classpath, copying its elements into place on this Classpath, while eliminating duplicate entries - * on the classpath. + * Overlay another classpath, copying its elements into place on this + * Classpath, while eliminating duplicate entries on the classpath. * - * @param cpOther - * the other classpath to overlay + * @param cpOther the other classpath to overlay */ public void overlay(Classpath cpOther) { diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 4e2d6115f9c..c42b93d7106 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -20,6 +20,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -30,7 +31,6 @@ import java.lang.reflect.Method; import java.net.ConnectException; import java.net.InetAddress; import java.net.Socket; -import java.security.Policy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -40,17 +40,15 @@ import java.util.List; import java.util.Properties; import java.util.Set; - /*-------------------------------------------*/ /** *

    - * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It - * allows an application to be started with the command "java -jar start.jar". + * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows an application to be started with + * the command "java -jar start.jar". *

    - * + * *

    - * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file - * obtained as a resource or file. + * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file obtained as a resource or file. *

    */ public class Main @@ -67,257 +65,240 @@ public class Main private boolean _dryRun = false; private boolean _exec = false; private final Config _config = new Config(); - private Set _sysProps = new HashSet(); - private List _jvmArgs = new ArrayList(); + private final Set _sysProps = new HashSet(); + private final List _jvmArgs = new ArrayList(); private String _startConfig = null; private String _jettyHome; - public static void main(String[] args) - { - Main main = new Main(); - main.parseCommandLine(args); - } - - public void parseCommandLine(String[] args) + public static void main(String[] args) { try { - List arguments = new ArrayList(); - - // add the command line args and look for start.ini args - boolean ini=false; - for (String arg : args) - { - if (arg.startsWith("--ini=")||arg.equals("--ini")) - { - ini=true; - if (arg.length()>6) - { - arguments.addAll(loadStartIni(arg.substring(6))); - continue; - } - } - else if (arg.startsWith("--config=")) - { - _startConfig=arg.substring(9); - } - else - { - arguments.add(arg); - } - } - - // if no non-option inis, add the start.ini - if (!ini) - { - arguments.addAll(0,loadStartIni(null)); - } - - // The XML Configuration Files to initialize with - List xmls = new ArrayList(); - - // Process the arguments - int startup=0; - for (String arg : arguments) - { - if ("--help".equals(arg) || "-?".equals(arg)) - { - _showUsage = true; - continue; - } - - if ("--stop".equals(arg)) - { - int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1")); - String key = Config.getProperty("STOP.KEY",null); - stop(port,key); - return; - } - - if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg)) - { - _dumpVersions = true; - continue; - } - - if ("--list-modes".equals(arg) || "--list-options".equals(arg)) - { - _listOptions = true; - continue; - } - - if ("--list-config".equals(arg)) - { - _listConfig=true; - continue; - } - - if ("--exec-print".equals(arg)||"--dry-run".equals(arg)) - { - _dryRun = true; - continue; - } - - if ("--exec".equals(arg)) - { - _exec = true; - continue; - } - - // Special internal indicator that jetty was started by the jetty.sh Daemon - if ("--daemon".equals(arg)) - { - File startDir = new File(System.getProperty("jetty.logs","logs")); - if (!startDir.exists() || !startDir.canWrite() ) - startDir = new File("."); - File startLog = new File(startDir,"start.log"); - if (!startLog.exists() && !startLog.createNewFile()) - { - // Output about error is lost in majority of cases. - System.err.println("Unable to create: " + startLog.getAbsolutePath()); - // Toss a unique exit code indicating this failure. - usageExit(ERR_LOGGING); - } - - if (!startLog.canWrite()) - { - // Output about error is lost in majority of cases. - System.err.println("Unable to write to: " + startLog.getAbsolutePath()); - // Toss a unique exit code indicating this failure. - usageExit(ERR_LOGGING); - } - PrintStream logger = new PrintStream(new FileOutputStream(startLog,false)); - System.setOut(logger); - System.setErr(logger); - System.out.println("Establishing start.log on " + new Date()); - continue; - } - - if (arg.startsWith("--pre=")) - { - xmls.add(startup++,arg.substring(6)); - continue; - } - - if (arg.startsWith("-D")) - { - String[] assign = arg.substring(2).split("=",2); - _sysProps.add(assign[0]); - switch(assign.length) - { - case 2: - System.setProperty(assign[0],assign[1]); - break; - case 1: - System.setProperty(assign[0],""); - break; - default: - break; - } - continue; - } - - if (arg.startsWith("-")) - { - _jvmArgs.add(arg); - continue; - } - - // Is this a Property? - if (arg.indexOf('=') >= 0) - { - String[] assign = arg.split("=",2); - - switch(assign.length) - { - case 2: - if ("OPTIONS".equals(assign[0])) - { - String opts[] = assign[1].split(","); - for (String opt : opts) - _config.addActiveOption(opt); - } - else - { - this._config.setProperty(assign[0],assign[1]); - } - break; - case 1: - this._config.setProperty(assign[0],null); - break; - default: - break; - } - - continue; - } - - // Anything else is considered an XML file. - if (xmls.contains(arg)) - { - System.out.println("WARN: Argument '"+arg+"' specified multiple times. Check start.ini?"); - System.out.println("Use \"java -jar start.jar --help\" for more information."); - } - xmls.add(arg); - } - - start(xmls); + Main main = new Main(); + List arguments = main.expandCommandLine(args); + List xmls = main.processCommandLine(arguments); + if (xmls!=null) + main.start(xmls); } - catch (Throwable t) - { - usageExit(t,ERR_UNKNOWN); - } - } - - /** - * If a start.ini is present in the CWD, then load it into the argument list. - */ - private List loadStartIni(String ini) - { - String jettyHome=System.getProperty("jetty.home"); - File startIniFile = ini==null?((jettyHome!=null)? new File(jettyHome,"start.ini"):new File("start.ini")):new File(ini); - if (!startIniFile.exists()) - { - if (ini != null) - { - System.err.println("Warning - can't find ini file: " + ini); - } - // No start.ini found, skip load. - return Collections.emptyList(); - } - - List args = new ArrayList(); - - FileReader reader = null; - BufferedReader buf = null; - try - { - reader = new FileReader(startIniFile); - buf = new BufferedReader(reader); - - String arg; - while ((arg = buf.readLine()) != null) - { - arg = arg.trim(); - if (arg.length() == 0 || arg.startsWith("#")) - { - continue; - } - args.add(arg); - } - } - catch (IOException e) + catch (Throwable e) { usageExit(e,ERR_UNKNOWN); } - finally + } + + Main() throws IOException + { + _jettyHome = System.getProperty("jetty.home","."); + _jettyHome = new File(_jettyHome).getCanonicalPath(); + } + + public List expandCommandLine(String[] args) throws Exception + { + List arguments = new ArrayList(); + + // add the command line args and look for start.ini args + boolean ini = false; + for (String arg : args) { - close(buf); - close(reader); + if (arg.startsWith("--ini=") || arg.equals("--ini")) + { + ini = true; + if (arg.length() > 6) + { + arguments.addAll(loadStartIni(new File(arg.substring(6)))); + continue; + } + } + else if (arg.startsWith("--config=")) + { + _startConfig = arg.substring(9); + } + else + { + arguments.add(arg); + } } - return args; + // if no non-option inis, add the start.ini and start.d + if (!ini) + { + List ini_args=new ArrayList(); + File start_ini = new File(_jettyHome,"start.ini"); + if (start_ini.exists()) + ini_args.addAll(loadStartIni(start_ini)); + + File start_d = new File(_jettyHome,"start.d"); + if (start_d.isDirectory()) + { + File[] inis = start_d.listFiles(new FilenameFilter() + { + public boolean accept(File dir, String name) + { + return name.toLowerCase().endsWith(".ini"); + } + }); + Arrays.sort(inis); + for (File i : inis) + ini_args.addAll(loadStartIni(i)); + } + arguments.addAll(0,ini_args); + } + + return arguments; + } + + public List processCommandLine(List arguments) throws Exception + { + // The XML Configuration Files to initialize with + List xmls = new ArrayList(); + + // Process the arguments + int startup = 0; + for (String arg : arguments) + { + if ("--help".equals(arg) || "-?".equals(arg)) + { + _showUsage = true; + continue; + } + + if ("--stop".equals(arg)) + { + int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1")); + String key = Config.getProperty("STOP.KEY",null); + stop(port,key); + return null; + } + + if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg)) + { + _dumpVersions = true; + continue; + } + + if ("--list-modes".equals(arg) || "--list-options".equals(arg)) + { + _listOptions = true; + continue; + } + + if ("--list-config".equals(arg)) + { + _listConfig = true; + continue; + } + + if ("--exec-print".equals(arg) || "--dry-run".equals(arg)) + { + _dryRun = true; + continue; + } + + if ("--exec".equals(arg)) + { + _exec = true; + continue; + } + + // Special internal indicator that jetty was started by the jetty.sh Daemon + if ("--daemon".equals(arg)) + { + File startDir = new File(System.getProperty("jetty.logs","logs")); + if (!startDir.exists() || !startDir.canWrite()) + startDir = new File("."); + File startLog = new File(startDir,"start.log"); + if (!startLog.exists() && !startLog.createNewFile()) + { + // Output about error is lost in majority of cases. + System.err.println("Unable to create: " + startLog.getAbsolutePath()); + // Toss a unique exit code indicating this failure. + usageExit(ERR_LOGGING); + } + + if (!startLog.canWrite()) + { + // Output about error is lost in majority of cases. + System.err.println("Unable to write to: " + startLog.getAbsolutePath()); + // Toss a unique exit code indicating this failure. + usageExit(ERR_LOGGING); + } + PrintStream logger = new PrintStream(new FileOutputStream(startLog,false)); + System.setOut(logger); + System.setErr(logger); + System.out.println("Establishing start.log on " + new Date()); + continue; + } + + if (arg.startsWith("--pre=")) + { + xmls.add(startup++,arg.substring(6)); + continue; + } + + if (arg.startsWith("-D")) + { + String[] assign = arg.substring(2).split("=",2); + _sysProps.add(assign[0]); + switch (assign.length) + { + case 2: + System.setProperty(assign[0],assign[1]); + break; + case 1: + System.setProperty(assign[0],""); + break; + default: + break; + } + continue; + } + + if (arg.startsWith("-")) + { + _jvmArgs.add(arg); + continue; + } + + // Is this a Property? + if (arg.indexOf('=') >= 0) + { + String[] assign = arg.split("=",2); + + switch (assign.length) + { + case 2: + if ("OPTIONS".equals(assign[0])) + { + String opts[] = assign[1].split(","); + for (String opt : opts) + _config.addActiveOption(opt); + } + else + { + this._config.setProperty(assign[0],assign[1]); + } + break; + case 1: + this._config.setProperty(assign[0],null); + break; + default: + break; + } + + continue; + } + + // Anything else is considered an XML file. + if (xmls.contains(arg)) + { + System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?"); + System.out.println("Use \"java -jar start.jar --help\" for more information."); + } + xmls.add(arg); + } + + return xmls; } private void usage() @@ -339,10 +320,10 @@ public class Main while ((line = buf.readLine()) != null) { - if (line.endsWith("@") && line.indexOf('@')!=line.lastIndexOf('@')) + if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@')) { - String indent=line.substring(0,line.indexOf("@")); - String info=line.substring(line.indexOf('@'),line.lastIndexOf('@')); + String indent = line.substring(0,line.indexOf("@")); + String info = line.substring(line.indexOf('@'),line.lastIndexOf('@')); if (info.equals("@OPTIONS")) { @@ -352,7 +333,7 @@ public class Main for (String option : sortedOptions) { - if ("*".equals(option) || option.trim().length()==0) + if ("*".equals(option) || option.trim().length() == 0) continue; System.out.print(indent); System.out.println(option); @@ -396,7 +377,7 @@ public class Main else if (info.equals("@STARTINI")) { List ini = loadStartIni(null); - if (ini!=null && ini.size()>0) + if (ini != null && ini.size() > 0) { for (String a : ini) { @@ -429,7 +410,7 @@ public class Main } public void invokeMain(ClassLoader classloader, String classname, List args) throws IllegalAccessException, InvocationTargetException, - NoSuchMethodException, ClassNotFoundException + NoSuchMethodException, ClassNotFoundException { Class invoked_class = null; @@ -462,10 +443,12 @@ public class Main String argArray[] = args.toArray(new String[0]); - Class[] method_param_types = new Class[] { argArray.getClass() }; + Class[] method_param_types = new Class[] + { argArray.getClass() }; Method main = invoked_class.getDeclaredMethod("main",method_param_types); - Object[] method_params = new Object[] { argArray }; + Object[] method_params = new Object[] + { argArray }; main.invoke(null,method_params); } @@ -492,13 +475,12 @@ public class Main // Setup Start / Stop Monitoring int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1")); String key = Config.getProperty("STOP.KEY",null); - Monitor monitor=new Monitor(port,key); - + Monitor monitor = new Monitor(port,key); // Load potential Config (start.config) List configuredXmls = loadConfig(xmls); - // No XML defined in start.config or command line. Can't execute. + // No XML defined in start.config or command line. Can't execute. if (configuredXmls.isEmpty()) { throw new FileNotFoundException("No XML configuration files specified in start.config or command line."); @@ -523,7 +505,7 @@ public class Main System.err.println("classloader.parent=" + cl.getParent()); System.err.println("properties=" + Config.getProperties()); } - + // Show the usage information and return if (_showUsage) { @@ -544,7 +526,7 @@ public class Main showAllOptionsWithVersions(classpath); return; } - + if (_listConfig) { listConfig(); @@ -557,7 +539,7 @@ public class Main System.out.println(buildCommandLine(classpath,configuredXmls)); return; } - + // execute Jetty in another JVM if (_exec) { @@ -565,11 +547,12 @@ public class Main final Process process = Runtime.getRuntime().exec(cmd); Runtime.getRuntime().addShutdownHook(new Thread() { + @Override public void run() { Config.debug("Destroying " + process); process.destroy(); - } + } }); copyInThread(process.getErrorStream(),System.err); copyInThread(process.getInputStream(),System.out); @@ -578,7 +561,7 @@ public class Main process.waitFor(); return; } - + if (_jvmArgs.size() > 0 || _sysProps.size() > 0) { System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec"); @@ -617,7 +600,7 @@ public class Main } } - private void copyInThread(final InputStream in,final OutputStream out) + private void copyInThread(final InputStream in, final OutputStream out) { new Thread(new Runnable() { @@ -625,23 +608,23 @@ public class Main { try { - byte[] buf=new byte[1024]; - int len=in.read(buf); - while(len>0) + byte[] buf = new byte[1024]; + int len = in.read(buf); + while (len > 0) { out.write(buf,0,len); - len=in.read(buf); + len = in.read(buf); } } - catch(IOException e) + catch (IOException e) { // e.printStackTrace(); } } - + }).start(); } - + private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException { if (!xmlFilename.toLowerCase().endsWith(".xml")) @@ -675,22 +658,22 @@ public class Main { StringBuilder cmd = new StringBuilder(); cmd.append(findJavaBin()); - for (String x:_jvmArgs) + for (String x : _jvmArgs) cmd.append(' ').append(x); cmd.append(" -Djetty.home=").append(_jettyHome); - for (String p:_sysProps) + for (String p : _sysProps) { cmd.append(" -D").append(p); - String v=System.getProperty(p); - if (v!=null && v.length()>0) + String v = System.getProperty(p); + if (v != null && v.length() > 0) cmd.append('=').append(v); } cmd.append(" -cp ").append(classpath.toString()); cmd.append(" ").append(_config.getMainClassname()); - + // Check if we need to pass properties as a file Properties properties = Config.getProperties(); - if (properties.size()>0) + if (properties.size() > 0) { File prop_file = File.createTempFile("start",".properties"); if (!_dryRun) @@ -698,7 +681,7 @@ public class Main properties.store(new FileOutputStream(prop_file),"start.jar properties"); cmd.append(" ").append(prop_file.getAbsolutePath()); } - + for (String xml : xmls) { cmd.append(' ').append(xml); @@ -875,11 +858,10 @@ public class Main private String getZipVersion(File element) { - // TODO - find version in zip file. Look for META-INF/MANIFEST.MF ? + // TODO - find version in zip file. Look for META-INF/MANIFEST.MF ? return ""; } - private List resolveXmlConfigs(List xmls) throws FileNotFoundException { List ret = new ArrayList(); @@ -896,15 +878,15 @@ public class Main InputStream cfgstream = null; try { - cfgstream=getConfigStream(); - byte[] buf=new byte[4096]; - - int len=0; - - while (len>=0) + cfgstream = getConfigStream(); + byte[] buf = new byte[4096]; + + int len = 0; + + while (len >= 0) { - len=cfgstream.read(buf); - if (len>0) + len = cfgstream.read(buf); + if (len > 0) System.out.write(buf,0,len); } } @@ -917,13 +899,13 @@ public class Main close(cfgstream); } } - + /** * Load Configuration. - * - * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to - * execute the {@link Class} specified by {@link Config#getMainClassname()} is executed. - * + * + * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by + * {@link Config#getMainClassname()} is executed. + * * @param xmls * the command line specified xml configuration options. * @return the list of xml configurations arriving via command line and start.config choices. @@ -935,13 +917,13 @@ public class Main { // Pass in xmls.size into Config so that conditions based on "nargs" work. _config.setArgCount(xmls.size()); - - cfgstream=getConfigStream(); - + + cfgstream = getConfigStream(); + // parse the config _config.parse(cfgstream); - - _jettyHome = Config.getProperty("jetty.home"); + + _jettyHome = Config.getProperty("jetty.home",_jettyHome); if (_jettyHome != null) { _jettyHome = new File(_jettyHome).getCanonicalPath(); @@ -975,12 +957,12 @@ public class Main private InputStream getConfigStream() throws FileNotFoundException { - String config=_startConfig; + String config = _startConfig; if (config == null || config.length() == 0) { config = System.getProperty("START","org/eclipse/jetty/start/start.config"); } - + Config.debug("config=" + config); // Look up config as resource first. @@ -991,11 +973,10 @@ public class Main { cfgstream = new FileInputStream(config); } - + return cfgstream; } - /** * Stop a running jetty instance. */ @@ -1038,7 +1019,6 @@ public class Main usageExit(e,ERR_UNKNOWN); } } - static void usageExit(Throwable t, int exit) { @@ -1048,6 +1028,7 @@ public class Main System.err.println(" java -jar start.jar --help # for more information"); System.exit(exit); } + static void usageExit(int exit) { System.err.println(); @@ -1055,4 +1036,53 @@ public class Main System.err.println(" java -jar start.jar --help # for more information"); System.exit(exit); } + + /** + * Convert a start.ini format file into an argument list. + */ + static List loadStartIni(File ini) + { + File startIniFile = ini; + if (!startIniFile.exists()) + { + if (ini != null) + { + System.err.println("Warning - can't find ini file: " + ini); + } + // No start.ini found, skip load. + return Collections.emptyList(); + } + + List args = new ArrayList(); + + FileReader reader = null; + BufferedReader buf = null; + try + { + reader = new FileReader(ini); + buf = new BufferedReader(reader); + + String arg; + while ((arg = buf.readLine()) != null) + { + arg = arg.trim(); + if (arg.length() == 0 || arg.startsWith("#")) + { + continue; + } + args.add(arg); + } + } + catch (IOException e) + { + // usageExit(e,ERR_UNKNOWN); + } + finally + { + Main.close(buf); + Main.close(reader); + } + + return args; + } } diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt index 7371be3f997..c4c85c4523d 100644 --- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt +++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt @@ -116,8 +116,11 @@ Available Configurations: Defaults: A start.ini file may be used to specify default arguments to start.jar, which are used if no command line arguments are provided and override - the defaults in the start.config file. If --ini options are provided on - the command line, then start.ini will no be read. The current start.ini - arguments are: + the defaults in the start.config file. If the directory start.d exists, + then multiple *.ini files will be read from that directory in alphabetical + order. If --ini options are provided on the command line, then start.ini + and start.d will not be read. + + The current start.ini arguments are: @STARTINI@ diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java new file mode 100644 index 00000000000..246771b4da6 --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java @@ -0,0 +1,76 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.start; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.net.URL; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +/* ------------------------------------------------------------ */ +/** + */ +public class MainTest +{ + /* ------------------------------------------------------------ */ + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception + { + System.setProperty("jetty.home",this.getClass().getResource("/jetty.home").getFile()); + } + + /** + * Test method for {@link org.eclipse.jetty.start.StartIniParser#loadStartIni(java.lang.String)}. + */ + @Test + public void testLoadStartIni() + { + URL startIni = this.getClass().getResource("/jetty.home/start.ini"); + String startIniFileName = startIni.getFile(); + List args = Main.loadStartIni(new File(startIniFileName)); + assertEquals("Expected 5 uncommented lines in start.ini",5,args.size()); + assertEquals("First uncommented line in start.ini doesn't match expected result","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0)); + } + + @Test + public void testExpandCommandLine() throws Exception + { + Main main = new Main(); + List args = main.expandCommandLine(new String[]{}); + assertEquals("start.ini OPTIONS","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0)); + assertEquals("start.d/jmx OPTIONS","OPTIONS=jmx",args.get(5)); + assertEquals("start.d/jmx XML","--pre=etc/jetty-jmx.xml",args.get(6)); + assertEquals("start.d/websocket OPTIONS","OPTIONS=websocket",args.get(7)); + } + + @Test + public void testProcessCommandLine() throws Exception + { + Main main = new Main(); + List args = main.expandCommandLine(new String[]{}); + List xmls = main.processCommandLine(args); + + assertEquals("jmx --pre","etc/jetty-jmx.xml",xmls.get(0)); + assertEquals("start.ini","etc/jetty.xml",xmls.get(1)); + assertEquals("start.d","etc/jetty-testrealm.xml",xmls.get(5)); + } + +} diff --git a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini new file mode 100644 index 00000000000..827e41bf43c --- /dev/null +++ b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini @@ -0,0 +1,22 @@ +#=========================================================== +# Additional Jetty start.jar arguments +# Each line of this file is prepended to the command line +# arguments # of a call to: +# java -jar start.jar [arg...] +#=========================================================== + + + +#=========================================================== +#----------------------------------------------------------- +OPTIONS=jmx +#----------------------------------------------------------- + + +#=========================================================== +# Configuration files. +# For a full list of available configuration files do +# java -jar start.jar --help +#----------------------------------------------------------- +--pre=etc/jetty-jmx.xml +#=========================================================== diff --git a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini new file mode 100644 index 00000000000..679a221dd71 --- /dev/null +++ b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini @@ -0,0 +1,13 @@ +#=========================================================== +# Additional Jetty start.jar arguments +# Each line of this file is prepended to the command line +# arguments # of a call to: +# java -jar start.jar [arg...] +#=========================================================== + + + +#=========================================================== +#----------------------------------------------------------- +OPTIONS=websocket +#----------------------------------------------------------- \ No newline at end of file diff --git a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini new file mode 100644 index 00000000000..59313d3b994 --- /dev/null +++ b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini @@ -0,0 +1 @@ +etc/jetty-testrealm.xml \ No newline at end of file diff --git a/jetty-start/src/test/resources/jetty.home/start.ini b/jetty-start/src/test/resources/jetty.home/start.ini new file mode 100644 index 00000000000..a9b724988ef --- /dev/null +++ b/jetty-start/src/test/resources/jetty.home/start.ini @@ -0,0 +1,65 @@ +#=========================================================== +# Jetty start.jar arguments +# Each line of this file is prepended to the command line +# arguments # of a call to: +# java -jar start.jar [arg...] +#=========================================================== + + + +#=========================================================== +# If the arguements in this file include JVM arguments +# (eg -Xmx512m) or JVM System properties (eg com.sun.???), +# then these will not take affect unless the --exec +# parameter is included or if the output from --dry-run +# is executed like: +# eval $(java -jar start.jar --dry-run) +# +# Below are some recommended options for Sun's JRE +#----------------------------------------------------------- +# --exec +# -Dorg.apache.jasper.compiler.disablejsr199=true +# -Dcom.sun.management.jmxremote +# -Dorg.eclipse.jetty.util.log.IGNORED=true +# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true +# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true +# -Xmx2000m +# -Xmn512m +# -verbose:gc +# -XX:+PrintGCDateStamps +# -XX:+PrintGCTimeStamps +# -XX:+PrintGCDetails +# -XX:+PrintTenuringDistribution +# -XX:+PrintCommandLineFlags +# -XX:+DisableExplicitGC +# -XX:+UseConcMarkSweepGC +# -XX:ParallelCMSThreads=2 +# -XX:+CMSClassUnloadingEnabled +# -XX:+UseCMSCompactAtFullCollection +# -XX:CMSInitiatingOccupancyFraction=80 +#----------------------------------------------------------- + + +#=========================================================== +# Start classpath OPTIONS. +# These control what classes are on the classpath +# for a full listing do +# java -jar start.jar --list-options +#----------------------------------------------------------- +OPTIONS=Server,jsp,resources,websocket,ext +#----------------------------------------------------------- + + +#=========================================================== +# Configuration files. +# For a full list of available configuration files do +# java -jar start.jar --help +#----------------------------------------------------------- +etc/jetty.xml +# etc/jetty-ssl.xml +# etc/jetty-requestlog.xml +etc/jetty-deploy.xml +#etc/jetty-overlay.xml +etc/jetty-webapps.xml +etc/jetty-contexts.xml +#=========================================================== diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index 4bfebb8257f..9e5dc45445a 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-util @@ -29,7 +29,7 @@ - + + + org.slf4j + slf4j-jdk14 + ${slf4j-version} + test + diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java index 6fbe1cebe1a..98e29502940 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java @@ -104,7 +104,7 @@ public class DateCache /** Set the timezone. * @param tz TimeZone */ - public void setTimeZone(TimeZone tz) + public synchronized void setTimeZone(TimeZone tz) { setTzFormatString(tz); if( _locale != null ) @@ -145,7 +145,7 @@ public class DateCache } /* ------------------------------------------------------------ */ - private void setTzFormatString(final TimeZone tz ) + private synchronized void setTzFormatString(final TimeZone tz ) { int zIndex = _formatString.indexOf( "ZZZ" ); if( zIndex >= 0 ) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index 06976d45832..91e4a03196d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -330,7 +330,7 @@ public class StringUtil /* ------------------------------------------------------------ */ public static boolean isUTF8(String charset) { - return charset==__UTF8||__UTF8.equalsIgnoreCase(charset)||__UTF8Alt.equalsIgnoreCase(charset); + return __UTF8.equalsIgnoreCase(charset)||__UTF8Alt.equalsIgnoreCase(charset); } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java index e6ae64bd30e..06741c0a130 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java @@ -16,10 +16,14 @@ package org.eclipse.jetty.util; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; +import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** Handles coding of MIME "x-www-form-urlencoded". @@ -40,9 +44,9 @@ import java.util.Map; * * @see java.net.URLEncoder */ -public class UrlEncoded extends MultiMap +public class UrlEncoded extends MultiMap implements Cloneable { - // private static final Logger LOG = Log.getLogger(UrlEncoded.class); + private static final Logger LOG = Log.getLogger(UrlEncoded.class); public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8); @@ -75,13 +79,13 @@ public class UrlEncoded extends MultiMap /* ----------------------------------------------------------------- */ public void decode(String query) { - decodeTo(query,this,ENCODING); + decodeTo(query,this,ENCODING,-1); } /* ----------------------------------------------------------------- */ public void decode(String query,String charset) { - decodeTo(query,this,charset); + decodeTo(query,this,charset,-1); } /* -------------------------------------------------------------- */ @@ -174,6 +178,15 @@ public class UrlEncoded extends MultiMap * @param content the string containing the encoded parameters */ public static void decodeTo(String content, MultiMap map, String charset) + { + decodeTo(content,map,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset, int maxKeys) { if (charset==null) charset=ENCODING; @@ -205,6 +218,11 @@ public class UrlEncoded extends MultiMap } key = null; value=null; + if (maxKeys>0 && map.size()>maxKeys) + { + LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys); + return; + } break; case '=': if (key!=null) @@ -267,50 +285,59 @@ public class UrlEncoded extends MultiMap { String key = null; String value = null; - + // TODO cache of parameter names ??? int end=offset+length; for (int i=offset;i0) - { - map.add(value,""); - } - key = null; - value=null; - break; - - case '=': - if (key!=null) - { + switch ((char)(0xff&b)) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append(b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + if (i+20 && map.size()>maxKeys) + { + LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys); + return; + } break; case '=': @@ -411,9 +444,10 @@ public class UrlEncoded extends MultiMap /** Decoded parameters to Map. * @param in InputSteam to read * @param map MultiMap to add parameters to - * @param maxLength maximum length of content to read 0r -1 for no limit + * @param maxLength maximum length of content to read or -1 for no limit + * @param maxLength maximum number of keys to read or -1 for no limit */ - public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength) + public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException { synchronized(map) @@ -443,6 +477,11 @@ public class UrlEncoded extends MultiMap } key = null; value=null; + if (maxKeys>0 && map.size()>maxKeys) + { + LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys); + return; + } break; case '=': @@ -488,25 +527,20 @@ public class UrlEncoded extends MultiMap } /* -------------------------------------------------------------- */ - public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException + public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException { InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); - StringBuffer buf = new StringBuffer(); - - int c; - int length=0; - if (maxLength<0) - maxLength=Integer.MAX_VALUE; - while ((c=input.read())>0 && length++ + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + **/ public abstract class Utf8Appendable { + private final char REPLACEMENT = '\ufffd'; + private static final int UTF8_ACCEPT = 0; + private static final int UTF8_REJECT = 12; + protected final Appendable _appendable; - protected int _more; - protected int _bits; + protected int _state = UTF8_ACCEPT; + + private static final byte[] BYTE_TABLE = + { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8 + }; + + private static final byte[] TRANS_TABLE = + { + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12 + }; + + private int _codep; public Utf8Appendable(Appendable appendable) { - _appendable=appendable; + _appendable = appendable; } public abstract int length(); - + + protected void reset() + { + _state = UTF8_ACCEPT; + } + public void append(byte b) { try { appendByte(b); } - catch(IOException e) - { - throw new RuntimeException(e); - } - } - - public void append(byte[] b,int offset, int length) - { - try - { - int end=offset+length; - for (int i=offset; imaxChars) + if (length() > maxChars) return false; appendByte(b[i]); } return true; } - catch(IOException e) + catch (IOException e) { throw new RuntimeException(e); } } - + protected void appendByte(byte b) throws IOException { - if (b>=0) + + if (b > 0 && _state == UTF8_ACCEPT) { - if (_more>0) - { - _appendable.append('?'); - _more=0; - _bits=0; - } - else - _appendable.append((char)(0x7f&b)); - } - else if (_more==0) - { - if ((b&0xc0)!=0xc0) - { - // 10xxxxxx - _appendable.append('?'); - _more=0; - _bits=0; - } - else - { - if ((b & 0xe0) == 0xc0) - { - //110xxxxx - _more=1; - _bits=b&0x1f; - } - else if ((b & 0xf0) == 0xe0) - { - //1110xxxx - _more=2; - _bits=b&0x0f; - } - else if ((b & 0xf8) == 0xf0) - { - //11110xxx - _more=3; - _bits=b&0x07; - } - else if ((b & 0xfc) == 0xf8) - { - //111110xx - _more=4; - _bits=b&0x03; - } - else if ((b & 0xfe) == 0xfc) - { - //1111110x - _more=5; - _bits=b&0x01; - } - else - { - throw new IllegalArgumentException("!utf8"); - } - } + _appendable.append((char)(b & 0xFF)); } else { - if ((b&0xc0)==0xc0) - { // 11?????? - _appendable.append('?'); - _more=0; - _bits=0; - throw new IllegalArgumentException("!utf8"); - } - else + int i = b & 0xFF; + int type = BYTE_TABLE[i]; + _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6); + int next = TRANS_TABLE[_state + type]; + + switch(next) { - // 10xxxxxx - _bits=(_bits<<6)|(b&0x3f); - if (--_more==0) - _appendable.append(new String(Character.toChars(_bits))); + case UTF8_ACCEPT: + _state=next; + if (_codep < Character.MIN_HIGH_SURROGATE) + { + _appendable.append((char)_codep); + } + else + { + for (char c : Character.toChars(_codep)) + _appendable.append(c); + } + break; + + case UTF8_REJECT: + String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12); + _codep=0; + _state = UTF8_ACCEPT; + _appendable.append(REPLACEMENT); + throw new NotUtf8Exception(reason); + + default: + _state=next; + } } } -} \ No newline at end of file + public boolean isUtf8SequenceComplete() + { + return _state == UTF8_ACCEPT; + } + + public static class NotUtf8Exception extends IllegalArgumentException + { + public NotUtf8Exception(String reason) + { + super("Not valid UTF8! "+reason); + } + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java index b6051d59435..b86058e584f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java @@ -4,71 +4,73 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import java.io.IOException; - /* ------------------------------------------------------------ */ -/** UTF-8 StringBuffer. +/** + * UTF-8 StringBuffer. * - * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append * UTF-8 encoded bytes, that are converted into characters. - * - * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before * state a character is appended to the string buffer. - * + * * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. - * The UTF-8 code was inspired by http://javolution.org - * - * This class is not synchronised and should probably be called Utf8StringBuilder + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ -public class Utf8StringBuffer extends Utf8Appendable +public class Utf8StringBuffer extends Utf8Appendable { final StringBuffer _buffer; - + public Utf8StringBuffer() { super(new StringBuffer()); - _buffer=(StringBuffer)_appendable; + _buffer = (StringBuffer)_appendable; } - + public Utf8StringBuffer(int capacity) { super(new StringBuffer(capacity)); - _buffer=(StringBuffer)_appendable; + _buffer = (StringBuffer)_appendable; } + @Override public int length() { return _buffer.length(); } - + + @Override public void reset() { + super.reset(); _buffer.setLength(0); - _more=0; - _bits=0; } - + public StringBuffer getStringBuffer() { - if (_more!=0) - throw new IllegalStateException("!utf8"); + checkState(); return _buffer; } - + @Override public String toString() { - if (_more!=0) - throw new IllegalStateException("!utf8"); + checkState(); return _buffer.toString(); } + + private void checkState() + { + if (!isUtf8SequenceComplete()) + throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String"); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java index f270021d957..09866884eae 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java @@ -4,70 +4,74 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import java.io.IOException; /* ------------------------------------------------------------ */ /** UTF-8 StringBuilder. * - * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append * UTF-8 encoded bytes, that are converted into characters. - * - * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before * state a character is appended to the string buffer. - * + * * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. - * The UTF-8 code was inspired by http://javolution.org - * + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * */ -public class Utf8StringBuilder extends Utf8Appendable +public class Utf8StringBuilder extends Utf8Appendable { final StringBuilder _buffer; - + public Utf8StringBuilder() { super(new StringBuilder()); _buffer=(StringBuilder)_appendable; } - + public Utf8StringBuilder(int capacity) { super(new StringBuilder(capacity)); _buffer=(StringBuilder)_appendable; } - + + @Override public int length() { return _buffer.length(); } - + + @Override public void reset() { + super.reset(); _buffer.setLength(0); - _more=0; - _bits=0; } - + public StringBuilder getStringBuilder() { - if (_more!=0) - throw new IllegalStateException("!utf8"); + checkState(); return _buffer; } - + @Override public String toString() { - if (_more!=0) - throw new IllegalStateException("!utf8"); + checkState(); return _buffer.toString(); } + + private void checkState() + { + if (!isUtf8SequenceComplete()) + throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String"); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java index 360a836d3d5..84824567afa 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java @@ -373,7 +373,7 @@ public class JSON @Deprecated public void appendJSON(final StringBuffer buffer, Convertible converter) { - appendJSON((StringBuffer)buffer,converter); + appendJSON((Appendable)buffer,converter); } /* ------------------------------------------------------------ */ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java index dab77534ae2..b0ef41ce86c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java @@ -188,7 +188,7 @@ public abstract class AbstractLifeCycle implements LifeCycle private void setStopped() { _state = __STOPPED; - LOG.debug(STOPPED+" {}",this); + LOG.debug("{} {}",STOPPED,this); for (Listener listener : _listeners) listener.lifeCycleStopped(this); } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java index bc9205bdf8c..8e14c84b7d0 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java @@ -6,8 +6,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jetty.util.log.Log; @@ -144,7 +142,7 @@ public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable t=(T)o; } } - if (count>1) + if (count>1 && LOG.isDebugEnabled()) LOG.debug("getBean({}) 1 of {}",clazz.getName(),count); return t; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java index 94d8e795dc1..789bbf77511 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java @@ -24,17 +24,13 @@ import java.util.logging.Level; *

    * *

    - * Honors the standard jetty system property "org.eclipse.jetty.util.log.DEBUG" to set logger into debug - * mode (defaults to false, set to "true" to enable) - *

    - * - *

    * You can also set the logger level using - * standard java.util.logging configuration against the name "org.eclipse.jetty.util.log". + * standard java.util.logging configuration. *

    */ public class JavaUtilLog implements Logger { + private Level configuredLevel; private java.util.logging.Logger _logger; public JavaUtilLog() @@ -45,8 +41,11 @@ public class JavaUtilLog implements Logger public JavaUtilLog(String name) { _logger = java.util.logging.Logger.getLogger(name); - if (Boolean.getBoolean("org.eclipse.jetty.util.log.DEBUG")) + if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false"))) + { _logger.setLevel(Level.FINE); + } + configuredLevel = _logger.getLevel(); } public String getName() @@ -91,7 +90,15 @@ public class JavaUtilLog implements Logger public void setDebugEnabled(boolean enabled) { - _logger.setLevel(Level.FINE); + if (enabled) + { + configuredLevel = _logger.getLevel(); + _logger.setLevel(Level.FINE); + } + else + { + _logger.setLevel(configuredLevel); + } } public void debug(String msg, Object... args) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java index 8fffd91802a..52bb112079c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java @@ -13,9 +13,15 @@ package org.eclipse.jetty.util.log; import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; - -/* ------------------------------------------------------------ */ +/** + * JettyAwareLogger is used to fix a FQCN bug that arises from how Jetty + * Log uses an indirect slf4j implementation. + * + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 + */ class JettyAwareLogger implements org.slf4j.Logger { private static final int DEBUG = org.slf4j.spi.LocationAwareLogger.DEBUG_INT; @@ -24,6 +30,7 @@ class JettyAwareLogger implements org.slf4j.Logger private static final int TRACE = org.slf4j.spi.LocationAwareLogger.TRACE_INT; private static final int WARN = org.slf4j.spi.LocationAwareLogger.WARN_INT; + private static final String FQCN = Slf4jLog.class.getName(); private final org.slf4j.spi.LocationAwareLogger _logger; public JettyAwareLogger(org.slf4j.spi.LocationAwareLogger logger) @@ -586,8 +593,19 @@ class JettyAwareLogger implements org.slf4j.Logger return _logger.toString(); } - private void log(Marker marker, int level, String msg, Object[] objArray, Throwable t) + private void log(Marker marker, int level, String msg, Object[] argArray, Throwable t) { - _logger.log(marker,"org.eclipse.jetty.util.log.Log",level, msg, objArray,t); + if (argArray == null) + { + // Simple SLF4J Message (no args) + _logger.log(marker,FQCN,level,msg,null,t); + } + else + { + // Don't assume downstream handles argArray properly. + // Do it the SLF4J way here to eliminate that as a bug. + FormattingTuple ft = MessageFormatter.arrayFormat(msg,argArray); + _logger.log(marker,FQCN,level,ft.getMessage(),null,t); + } } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java index 400805b55f3..74f8e5fa66d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java @@ -13,10 +13,16 @@ package org.eclipse.jetty.util.log; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Method; +import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Properties; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Loader; /** @@ -40,74 +46,130 @@ public class Log public final static String EXCEPTION= "EXCEPTION "; public final static String IGNORED= "IGNORED "; + /** + * Logging Configuration Properties + */ + protected static Properties __props; + /** + * The {@link Logger} implementation class name + */ public static String __logClass; + /** + * Legacy flag indicating if {@link Log#ignore(Throwable)} methods produce any output in the {@link Logger}s + */ public static boolean __ignored; static { + /* Instantiate a default configuration properties (empty) + */ + __props = new Properties(); + AccessController.doPrivileged(new PrivilegedAction() { public Object run() { - __logClass = System.getProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.Slf4jLog"); - __ignored = Boolean.parseBoolean(System.getProperty("org.eclipse.jetty.util.log.IGNORED", "false")); + /* First see if the jetty-logging.properties object exists in the classpath. + * This is an optional feature used by embedded mode use, and test cases to allow for early + * configuration of the Log class in situations where access to the System.properties are + * either too late or just impossible. + */ + URL testProps = Log.class.getClassLoader().getResource("jetty-logging.properties"); + if (testProps != null) + { + InputStream in = null; + try + { + in = testProps.openStream(); + __props.load(in); + } + catch (IOException e) + { + System.err.println("Unable to load " + testProps); + e.printStackTrace(System.err); + } + finally + { + IO.close(in); + } + } + + /* Now load the System.properties as-is into the __props, these values will override + * any key conflicts in __props. + */ + @SuppressWarnings("unchecked") + Enumeration systemKeyEnum = (Enumeration)System.getProperties().propertyNames(); + while (systemKeyEnum.hasMoreElements()) + { + String key = systemKeyEnum.nextElement(); + __props.setProperty(key,System.getProperty(key)); + } + + /* Now use the configuration properties to configure the Log statics + */ + __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog"); + __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false")); return null; } }); } - private static Logger __log; + private static Logger LOG; private static boolean __initialized; public static boolean initialized() { - if (__log != null) + if (LOG != null) + { return true; + } synchronized (Log.class) { if (__initialized) - return __log != null; + { + return LOG != null; + } __initialized = true; } try { Class log_class = Loader.loadClass(Log.class, __logClass); - if (__log == null || !__log.getClass().equals(log_class)) + if (LOG == null || !LOG.getClass().equals(log_class)) { - __log = (Logger)log_class.newInstance(); - __log.debug("Logging to {} via {}", __log, log_class.getName()); + LOG = (Logger)log_class.newInstance(); + LOG.debug("Logging to {} via {}", LOG, log_class.getName()); } } - catch(NoClassDefFoundError e) - { - initStandardLogging(e); - } - catch(Exception e) + catch(Throwable e) { + // Unable to load specified Logger implementation, default to standard logging. initStandardLogging(e); } - return __log != null; + return LOG != null; } private static void initStandardLogging(Throwable e) { Class log_class; if(e != null && __ignored) + { e.printStackTrace(); - if (__log == null) + } + + if (LOG == null) { log_class = StdErrLog.class; - __log = new StdErrLog(); - __log.debug("Logging to {} via {}", __log, log_class.getName()); + LOG = new StdErrLog(); + LOG.debug("Logging to {} via {}", LOG, log_class.getName()); } } public static void setLog(Logger log) { - Log.__log = log; + Log.LOG = log; } /** @@ -117,22 +179,18 @@ public class Log public static Logger getLog() { initialized(); - return __log; + return LOG; } - + /** * Get the root logger. * @return the root logger */ public static Logger getRootLogger() { initialized(); - return __log; + return LOG; } - /** - * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} - */ - @Deprecated static boolean isIgnored() { return __ignored; @@ -184,7 +242,7 @@ public class Log { if (!isDebugEnabled()) return; - __log.debug(EXCEPTION, th); + LOG.debug(EXCEPTION, th); } /** @@ -195,7 +253,7 @@ public class Log { if (!initialized()) return; - __log.debug(msg); + LOG.debug(msg); } /** @@ -206,7 +264,7 @@ public class Log { if (!initialized()) return; - __log.debug(msg, arg); + LOG.debug(msg, arg); } /** @@ -217,7 +275,7 @@ public class Log { if (!initialized()) return; - __log.debug(msg, arg0, arg1); + LOG.debug(msg, arg0, arg1); } /** @@ -233,7 +291,7 @@ public class Log { if (!initialized()) return; - __log.ignore(thrown); + LOG.ignore(thrown); } /** @@ -244,7 +302,7 @@ public class Log { if (!initialized()) return; - __log.info(msg); + LOG.info(msg); } /** @@ -255,7 +313,7 @@ public class Log { if (!initialized()) return; - __log.info(msg, arg); + LOG.info(msg, arg); } /** @@ -266,7 +324,7 @@ public class Log { if (!initialized()) return; - __log.info(msg, arg0, arg1); + LOG.info(msg, arg0, arg1); } /** @@ -277,7 +335,7 @@ public class Log { if (!initialized()) return false; - return __log.isDebugEnabled(); + return LOG.isDebugEnabled(); } /** @@ -288,7 +346,7 @@ public class Log { if (!initialized()) return; - __log.warn(msg); + LOG.warn(msg); } /** @@ -299,7 +357,7 @@ public class Log { if (!initialized()) return; - __log.warn(msg, arg); + LOG.warn(msg, arg); } /** @@ -310,7 +368,7 @@ public class Log { if (!initialized()) return; - __log.warn(msg, arg0, arg1); + LOG.warn(msg, arg0, arg1); } /** @@ -321,7 +379,7 @@ public class Log { if (!initialized()) return; - __log.warn(msg, th); + LOG.warn(msg, th); } /** @@ -332,12 +390,12 @@ public class Log { if (!initialized()) return; - __log.warn(EXCEPTION, th); + LOG.warn(EXCEPTION, th); } - + /** * Obtain a named Logger based on the fully qualified class name. - * + * * @param clazz * the class to base the Logger name off of * @return the Logger with the given name @@ -357,6 +415,6 @@ public class Log if (!initialized()) return null; - return name == null ? __log : __log.getLogger(name); + return name == null ? LOG : LOG.getLogger(name); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java index 82c0b7e9dce..2b8e268d1a4 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java @@ -25,7 +25,6 @@ public class Slf4jLog implements Logger public Slf4jLog() throws Exception { this("org.eclipse.jetty.util.log"); - } public Slf4jLog(String name) @@ -42,6 +41,9 @@ public class Slf4jLog implements Logger } org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name ); + + // Fix LocationAwareLogger use to indicate FQCN of this class - + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 if (logger instanceof org.slf4j.spi.LocationAwareLogger) { _logger = new JettyAwareLogger((org.slf4j.spi.LocationAwareLogger)logger); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java index 7fd81d823a7..72192728e0d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java @@ -13,64 +13,81 @@ package org.eclipse.jetty.util.log; +import java.io.PrintStream; import java.security.AccessControlException; +import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.eclipse.jetty.util.DateCache; /** - * StdErr Logging. This implementation of the Logging facade sends all logs to - * StdErr with minimal formatting. + * StdErr Logging. This implementation of the Logging facade sends all logs to StdErr with minimal formatting. *

    - * If the system property "org.eclipse.jetty.util.log.DEBUG" is set, then debug - * logs are printed if stderr is being used. For named debuggers, the system - * property name+".DEBUG" is checked. If it is not not set, then - * "org.eclipse.jetty.util.log.DEBUG" is used as the default. + * If the system property "org.eclipse.jetty.LEVEL" is set to one of the following (ALL, DEBUG, INFO, WARN), then set + * the eclipse jetty root level logger level to that specified level. (Default level is INFO) *

    - * If the system property "org.eclipse.jetty.util.log.SOURCE" is set, then the - * source method/file of a log is logged. For named debuggers, the system - * property name+".SOURCE" is checked. If it is not not set, then + * If the system property "org.eclipse.jetty.util.log.SOURCE" is set, then the source method/file of a log is logged. + * For named debuggers, the system property name+".SOURCE" is checked. If it is not not set, then * "org.eclipse.jetty.util.log.SOURCE" is used as the default. *

    - * If the system property "org.eclipse.jetty.util.log.LONG" is set, then the - * full, unabbreviated name of the logger is used for logging. - * For named debuggers, the system property name+".LONG" is checked. - * If it is not not set, then "org.eclipse.jetty.util.log.LONG" is used as the default. + * If the system property "org.eclipse.jetty.util.log.LONG" is set, then the full, unabbreviated name of the logger is + * used for logging. For named debuggers, the system property name+".LONG" is checked. If it is not not set, then + * "org.eclipse.jetty.util.log.LONG" is used as the default. */ public class StdErrLog implements Logger { + private static final String EOL = System.getProperty("line.separator"); private static DateCache _dateCache; + private static Properties __props = Log.__props; - private final static boolean __debug = Boolean.parseBoolean( - System.getProperty("org.eclipse.jetty.util.log.DEBUG", - System.getProperty("org.eclipse.jetty.util.log.stderr.DEBUG", "false"))); - private final static boolean __source = Boolean.parseBoolean( - System.getProperty("org.eclipse.jetty.util.log.SOURCE", - System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false"))); - private final static boolean __long = Boolean.parseBoolean( - System.getProperty("org.eclipse.jetty.util.log.stderr.LONG", "false")); + private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE", + Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false"))); + private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false")); + + /** + * Tracking for child loggers only. + */ + private final static ConcurrentMap __loggers = new ConcurrentHashMap(); - private final static ConcurrentMap __loggers = new ConcurrentHashMap(); - static { + String deprecatedProperties[] = + { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" }; + + // Toss a message to users about deprecated system properties + for (String deprecatedProp : deprecatedProperties) + { + if (System.getProperty(deprecatedProp) != null) + { + System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp); + } + } + try { _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss"); } catch (Exception x) { - x.printStackTrace(); + x.printStackTrace(System.err); } } - private boolean _debug = __debug; + public static final int LEVEL_ALL = 0; + public static final int LEVEL_DEBUG = 1; + public static final int LEVEL_INFO = 2; + public static final int LEVEL_WARN = 3; + + private int _level = LEVEL_INFO; + // Level that this Logger was configured as (remembered in special case of .setDebugEnabled()) + private int _configuredLevel; + private PrintStream _stderr = System.err; private boolean _source = __source; // Print the long form names, otherwise use abbreviated private boolean _printLongNames = __long; // The full log name, as provided by the system. - private final String _name; + private final String _name; // The abbreviated log name (used by default, unless _long is specified) private final String _abbrevname; private boolean _hideStacks = false; @@ -82,39 +99,108 @@ public class StdErrLog implements Logger public StdErrLog(String name) { - this._name = name == null ? "" : name; + this(name,__props); + } + + public StdErrLog(String name, Properties props) + { + __props = props; + this._name = name == null?"":name; this._abbrevname = condensePackageString(this._name); + this._level = getLoggingLevel(props,this._name); + this._configuredLevel = this._level; try { - _debug = Boolean.parseBoolean(System.getProperty(_name + ".DEBUG", Boolean.toString(_debug))); - } - catch (AccessControlException ace) - { - _debug = __debug; - } - - try - { - _source = Boolean.parseBoolean(System.getProperty(_name + ".SOURCE", Boolean.toString(_source))); + _source = Boolean.parseBoolean(props.getProperty(_name + ".SOURCE",Boolean.toString(_source))); } catch (AccessControlException ace) { _source = __source; } } - + + /** + * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to + * shortest. + * + * @param props + * the properties to check + * @param name + * the name to get log for + * @return the logging level + */ + public static int getLoggingLevel(Properties props, final String name) + { + // Calculate the level this named logger should operate under. + // Checking with FQCN first, then each package segment from longest to shortest. + String nameSegment = name; + + while ((nameSegment != null) && (nameSegment.length() > 0)) + { + String levelStr = props.getProperty(nameSegment + ".LEVEL"); + // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr); + int level = getLevelId(nameSegment + ".LEVEL",levelStr); + if (level != (-1)) + { + return level; + } + + // Trim and try again. + int idx = nameSegment.lastIndexOf('.'); + if (idx >= 0) + { + nameSegment = nameSegment.substring(0,idx); + } + else + { + nameSegment = null; + } + } + + // Default Logging Level + return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO")); + } + + protected static int getLevelId(String levelSegment, String levelName) + { + if (levelName == null) + { + return -1; + } + String levelStr = levelName.trim(); + if ("ALL".equalsIgnoreCase(levelStr)) + { + return LEVEL_ALL; + } + else if ("DEBUG".equalsIgnoreCase(levelStr)) + { + return LEVEL_DEBUG; + } + else if ("INFO".equalsIgnoreCase(levelStr)) + { + return LEVEL_INFO; + } + else if ("WARN".equalsIgnoreCase(levelStr)) + { + return LEVEL_WARN; + } + + System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values."); + return -1; + } + /** * Condenses a classname by stripping down the package name to just the first character of each package name - * segment. + * segment.Configured *

    - * + * *

          * Examples:
          * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
          * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
          * 
    - * + * * @param classname * the fully qualified class name * @return the condensed name @@ -139,7 +225,7 @@ public class StdErrLog implements Logger { return _name; } - + public void setPrintLongNames(boolean printLongNames) { this._printLongNames = printLongNames; @@ -161,7 +247,9 @@ public class StdErrLog implements Logger } /* ------------------------------------------------------------ */ - /** Is the source of a log, logged + /** + * Is the source of a log, logged + * * @return true if the class, method, file and line number of a log is logged. */ public boolean isSource() @@ -170,8 +258,11 @@ public class StdErrLog implements Logger } /* ------------------------------------------------------------ */ - /** Set if a log source is logged. - * @param source true if the class, method, file and line number of a log is logged. + /** + * Set if a log source is logged. + * + * @param source + * true if the class, method, file and line number of a log is logged. */ public void setSource(boolean source) { @@ -180,90 +271,161 @@ public class StdErrLog implements Logger public void warn(String msg, Object... args) { - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":WARN:", msg, args); - System.err.println(buffer); + if (_level <= LEVEL_WARN) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":WARN:",msg,args); + _stderr.println(buffer); + } } public void warn(Throwable thrown) { - warn("", thrown); + warn("",thrown); } public void warn(String msg, Throwable thrown) { - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":WARN:", msg, thrown); - System.err.println(buffer); + if (_level <= LEVEL_WARN) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":WARN:",msg,thrown); + _stderr.println(buffer); + } } public void info(String msg, Object... args) { - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":INFO:", msg, args); - System.err.println(buffer); + if (_level <= LEVEL_INFO) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":INFO:",msg,args); + _stderr.println(buffer); + } } public void info(Throwable thrown) { - info("", thrown); + info("",thrown); } public void info(String msg, Throwable thrown) { - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":INFO:", msg, thrown); - System.err.println(buffer); + if (_level <= LEVEL_INFO) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":INFO:",msg,thrown); + _stderr.println(buffer); + } } public boolean isDebugEnabled() { - return _debug; + return (_level <= LEVEL_DEBUG); } + /** + * Legacy interface where a programmatic configuration of the logger level + * is done as a wholesale approach. + */ public void setDebugEnabled(boolean enabled) { - _debug = enabled; + if (enabled) + { + synchronized (__loggers) + { + this._level = LEVEL_DEBUG; + + // Boot stomp all cached log levels to DEBUG + for(StdErrLog log: __loggers.values()) + { + log._level = LEVEL_DEBUG; + } + } + } + else + { + synchronized (__loggers) + { + this._level = this._configuredLevel; + + // restore all cached log configured levels + for(StdErrLog log: __loggers.values()) + { + log._level = log._configuredLevel; + } + } + } + } + + public int getLevel() + { + return _level; + } + + /** + * Set the level for this logger. + *

    + * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO}, + * {@link StdErrLog#LEVEL_WARN}) + * + * @param level + * the level to set the logger to + */ + public void setLevel(int level) + { + this._level = level; + } + + public void setStdErrStream(PrintStream stream) + { + this._stderr = stream; } public void debug(String msg, Object... args) { - if (!_debug) - return; - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":DBUG:", msg, args); - System.err.println(buffer); + if (_level <= LEVEL_DEBUG) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":DBUG:",msg,args); + _stderr.println(buffer); + } } public void debug(Throwable thrown) { - debug("", thrown); + debug("",thrown); } public void debug(String msg, Throwable thrown) { - if (!_debug) - return; - StringBuilder buffer = new StringBuilder(64); - format(buffer, ":DBUG:", msg, thrown); - System.err.println(buffer); + if (_level <= LEVEL_DEBUG) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":DBUG:",msg,thrown); + _stderr.println(buffer); + } } private void format(StringBuilder buffer, String level, String msg, Object... args) { String d = _dateCache.now(); int ms = _dateCache.lastMs(); - tag(buffer, d, ms, level); - format(buffer, msg, args); + tag(buffer,d,ms,level); + format(buffer,msg,args); } private void format(StringBuilder buffer, String level, String msg, Throwable thrown) { - format(buffer, level, msg); + format(buffer,level,msg); if (isHideStacks()) - format(buffer, String.valueOf(thrown)); + { + format(buffer,String.valueOf(thrown)); + } else - format(buffer, thrown); + { + format(buffer,thrown); + } } private void tag(StringBuilder buffer, String d, int ms, String tag) @@ -271,36 +433,52 @@ public class StdErrLog implements Logger buffer.setLength(0); buffer.append(d); if (ms > 99) + { buffer.append('.'); + } else if (ms > 9) + { buffer.append(".0"); + } else + { buffer.append(".00"); + } buffer.append(ms).append(tag); - if(_printLongNames) { + if (_printLongNames) + { buffer.append(_name); - } else { + } + else + { buffer.append(_abbrevname); } buffer.append(':'); if (_source) { Throwable source = new Throwable(); - StackTraceElement[] frames = source.getStackTrace(); - for (int i=0;i _excludeProtocols = new HashSet(); + // private final Set _excludeProtocols = new HashSet(Collections.singleton("SSLv2Hello")); + /** Included protocols. */ + private Set _includeProtocols = null; + + /** Excluded cipher suites. */ + private final Set _excludeCipherSuites = new HashSet(); + /** Included cipher suites. */ + private Set _includeCipherSuites = null; + + /** Keystore path. */ + private String _keyStorePath; + /** Keystore provider name */ + private String _keyStoreProvider; + /** Keystore type */ + private String _keyStoreType = "JKS"; + /** Keystore input stream */ + private InputStream _keyStoreInputStream; + + /** SSL certificate alias */ + private String _certAlias; + + /** Truststore path */ + private String _trustStorePath; + /** Truststore provider name */ + private String _trustStoreProvider; + /** Truststore type */ + private String _trustStoreType = "JKS"; + /** Truststore input stream */ + private InputStream _trustStoreInputStream; + + /** Set to true if client certificate authentication is required */ + private boolean _needClientAuth = false; + /** Set to true if client certificate authentication is desired */ + private boolean _wantClientAuth = false; + + /** Set to true if renegotiation is allowed */ + private boolean _allowRenegotiate = true; + + /** Keystore password */ + private transient Password _keyStorePassword; + /** Key manager password */ + private transient Password _keyManagerPassword; + /** Truststore password */ + private transient Password _trustStorePassword; + + /** SSL provider name */ + private String _sslProvider; + /** SSL protocol name */ + private String _sslProtocol = "TLS"; + + /** SecureRandom algorithm */ + private String _secureRandomAlgorithm; + /** KeyManager factory algorithm */ + private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM; + /** TrustManager factory algorithm */ + private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM; + + /** Set to true if SSL certificate validation is required */ + private boolean _validateCerts; + /** Set to true if SSL certificate of the peer validation is required */ + private boolean _validatePeerCerts; + /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */ + private int _maxCertPathLength = -1; + /** Path to file that contains Certificate Revocation List */ + private String _crlPath; + /** Set to true to enable CRL Distribution Points (CRLDP) support */ + private boolean _enableCRLDP = false; + /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */ + private boolean _enableOCSP = false; + /** Location of OCSP Responder */ + private String _ocspResponderURL; + + /** SSL keystore */ + private KeyStore _keyStore; + /** SSL truststore */ + private KeyStore _trustStore; + /** Set to true to enable SSL Session caching */ + private boolean _sessionCachingEnabled = true; + /** SSL session cache size */ + private int _sslSessionCacheSize; + /** SSL session timeout */ + private int _sslSessionTimeout; + + /** SSL context */ + private SSLContext _context; + + private boolean _trustAll; + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * Default constructor for use in XmlConfiguration files + */ + public SslContextFactory() + { + _trustAll=true; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * Default constructor for use in XmlConfiguration files + */ + public SslContextFactory(boolean trustAll) + { + _trustAll=trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * @param keyStorePath default keystore location + */ + public SslContextFactory(String keyStorePath) + { + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * Create the SSLContext object and start the lifecycle + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + if (_context == null) + { + if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null && + _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null ) + { + TrustManager[] trust_managers=null; + + if (_trustAll) + { + LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!"); + // Create a trust manager that does not validate certificate chains + TrustManager trustAllCerts = new X509TrustManager() + { + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + }; + trust_managers = new TrustManager[] { trustAllCerts }; + } + + SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); + _context = SSLContext.getInstance(_sslProtocol); + _context.init(null, trust_managers, secureRandom); + } + else + { + // verify that keystore and truststore + // parameters are set up correctly + checkKeyStore(); + + KeyStore keyStore = loadKeyStore(); + KeyStore trustStore = loadTrustStore(); + + Collection crls = loadCRL(_crlPath); + + if (_validateCerts && keyStore != null) + { + if (_certAlias == null) + { + List aliases = Collections.list(keyStore.aliases()); + _certAlias = aliases.size() == 1 ? aliases.get(0) : null; + } + + Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias); + if (cert == null) + { + throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias)); + } + + CertificateValidator validator = new CertificateValidator(trustStore, crls); + validator.setMaxCertPathLength(_maxCertPathLength); + validator.setEnableCRLDP(_enableCRLDP); + validator.setEnableOCSP(_enableOCSP); + validator.setOcspResponderURL(_ocspResponderURL); + validator.validate(keyStore, cert); + } + + KeyManager[] keyManagers = getKeyManagers(keyStore); + TrustManager[] trustManagers = getTrustManagers(trustStore,crls); + + SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); + _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider); + _context.init(keyManagers,trustManagers,secureRandom); + + SSLEngine engine=newSslEngine(); + + LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols())); + if (LOG.isDebugEnabled()) + LOG.debug("Enabled Ciphers {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites())); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getExcludeProtocols() + { + return _excludeProtocols.toArray(new String[_excludeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param Protocols + * The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setExcludeProtocols(String... protocols) + { + checkNotStarted(); + + _excludeProtocols.clear(); + _excludeProtocols.addAll(Arrays.asList(protocols)); + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void addExcludeProtocols(String... protocol) + { + checkNotStarted(); + _excludeProtocols.addAll(Arrays.asList(protocol)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getIncludeProtocols() + { + return _includeProtocols.toArray(new String[_includeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param Protocols + * The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setIncludeProtocols(String... protocols) + { + checkNotStarted(); + + _includeProtocols = new HashSet(Arrays.asList(protocols)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of cipher suite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public String[] getExcludeCipherSuites() + { + return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites + * The array of cipher suite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void setExcludeCipherSuites(String... cipherSuites) + { + checkNotStarted(); + _excludeCipherSuites.clear(); + _excludeCipherSuites.addAll(Arrays.asList(cipherSuites)); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void addExcludeCipherSuites(String... cipher) + { + checkNotStarted(); + _excludeCipherSuites.addAll(Arrays.asList(cipher)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of cipher suite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public String[] getIncludeCipherSuites() + { + return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites + * The array of cipher suite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void setIncludeCipherSuites(String... cipherSuites) + { + checkNotStarted(); + + _includeCipherSuites = new HashSet(Arrays.asList(cipherSuites)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The file or URL of the SSL Key store. + */ + public String getKeyStorePath() + { + return _keyStorePath; + } + + /* ------------------------------------------------------------ */ + @Deprecated + public String getKeyStore() + { + return _keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStorePath + * The file or URL of the SSL Key store. + */ + public void setKeyStorePath(String keyStorePath) + { + checkNotStarted(); + + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStorePath + * @deprecated Use {@link #setKeyStorePath(String)} + */ + @Deprecated + public void setKeyStore(String keyStorePath) + { + checkNotStarted(); + + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @return The provider of the key store + */ + public String getKeyStoreProvider() + { + return _keyStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStoreProvider + * The provider of the key store + */ + public void setKeyStoreProvider(String keyStoreProvider) + { + checkNotStarted(); + + _keyStoreProvider = keyStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The type of the key store (default "JKS") + */ + public String getKeyStoreType() + { + return (_keyStoreType); + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStoreType + * The type of the key store (default "JKS") + */ + public void setKeyStoreType(String keyStoreType) + { + checkNotStarted(); + + _keyStoreType = keyStoreType; + } + + /* ------------------------------------------------------------ */ + /** Get the _keyStoreInputStream. + * @return the _keyStoreInputStream + * + * @deprecated + */ + @Deprecated + public InputStream getKeyStoreInputStream() + { + checkKeyStore(); + + return _keyStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** Set the keyStoreInputStream. + * @param keyStoreInputStream the InputStream to the KeyStore + * + * @deprecated Use {@link #setKeyStore(KeyStore)} + */ + @Deprecated + public void setKeyStoreInputStream(InputStream keyStoreInputStream) + { + checkNotStarted(); + + _keyStoreInputStream = keyStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** + * @return Alias of SSL certificate for the connector + */ + public String getCertAlias() + { + return _certAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @param certAlias + * Alias of SSL certificate for the connector + */ + public void setCertAlias(String certAlias) + { + checkNotStarted(); + + _certAlias = certAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @return The file name or URL of the trust store location + */ + public String getTrustStore() + { + return _trustStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStorePath + * The file name or URL of the trust store location + */ + public void setTrustStore(String trustStorePath) + { + checkNotStarted(); + + _trustStorePath = trustStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @return The provider of the trust store + */ + public String getTrustStoreProvider() + { + return _trustStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStoreProvider + * The provider of the trust store + */ + public void setTrustStoreProvider(String trustStoreProvider) + { + checkNotStarted(); + + _trustStoreProvider = trustStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The type of the trust store (default "JKS") + */ + public String getTrustStoreType() + { + return _trustStoreType; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStoreType + * The type of the trust store (default "JKS") + */ + public void setTrustStoreType(String trustStoreType) + { + checkNotStarted(); + + _trustStoreType = trustStoreType; + } + + /* ------------------------------------------------------------ */ + /** Get the _trustStoreInputStream. + * @return the _trustStoreInputStream + * + * @deprecated + */ + @Deprecated + public InputStream getTrustStoreInputStream() + { + checkKeyStore(); + + return _trustStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** Set the _trustStoreInputStream. + * @param trustStoreInputStream the InputStream to the TrustStore + * + * @deprecated + */ + @Deprecated + public void setTrustStoreInputStream(InputStream trustStoreInputStream) + { + checkNotStarted(); + + _trustStoreInputStream = trustStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + */ + public boolean getNeedClientAuth() + { + return _needClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @param needClientAuth + * True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + */ + public void setNeedClientAuth(boolean needClientAuth) + { + checkNotStarted(); + + _needClientAuth = needClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + */ + public boolean getWantClientAuth() + { + return _wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @param wantClientAuth + * True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + */ + public void setWantClientAuth(boolean wantClientAuth) + { + checkNotStarted(); + + _wantClientAuth = wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificate has to be validated + * @deprecated + */ + @Deprecated + public boolean getValidateCerts() + { + return _validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificate has to be validated + */ + public boolean isValidateCerts() + { + return _validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @param validateCerts + * true if SSL certificates have to be validated + */ + public void setValidateCerts(boolean validateCerts) + { + checkNotStarted(); + + _validateCerts = validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificates of the peer have to be validated + */ + public boolean isValidatePeerCerts() + { + return _validatePeerCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @param validatePeerCerts + * true if SSL certificates of the peer have to be validated + */ + public void setValidatePeerCerts(boolean validatePeerCerts) + { + checkNotStarted(); + + _validatePeerCerts = validatePeerCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban + * of renegotiates in u19 and with RFC5746 in u22. + * + * @param allowRenegotiate + * true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + checkNotStarted(); + + _allowRenegotiate = allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password for the key store + */ + public void setKeyStorePassword(String password) + { + checkNotStarted(); + + _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password (if any) for the specific key within the key store + */ + public void setKeyManagerPassword(String password) + { + checkNotStarted(); + + _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password for the trust store + */ + public void setTrustStorePassword(String password) + { + checkNotStarted(); + + _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + */ + public String getProvider() + { + return _sslProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param provider + * The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + */ + public void setProvider(String provider) + { + checkNotStarted(); + + _sslProvider = provider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSL protocol (default "TLS") passed to + * {@link SSLContext#getInstance(String, String)} + */ + public String getProtocol() + { + return _sslProtocol; + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol + * The SSL protocol (default "TLS") passed to + * {@link SSLContext#getInstance(String, String)} + */ + public void setProtocol(String protocol) + { + checkNotStarted(); + + _sslProtocol = protocol; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to + * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + */ + public String getSecureRandomAlgorithm() + { + return _secureRandomAlgorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to + * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + */ + public void setSecureRandomAlgorithm(String algorithm) + { + checkNotStarted(); + + _secureRandomAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} + */ + public String getSslKeyManagerFactoryAlgorithm() + { + return (_keyManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} + */ + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + checkNotStarted(); + + _keyManagerFactoryAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + */ + public String getTrustManagerFactoryAlgorithm() + { + return (_trustManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if all certificates should be trusted if there is no KeyStore or TrustStore + */ + public boolean isTrustAll() + { + return _trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore + */ + public void setTrustAll(boolean trustAll) + { + _trustAll = trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + * Use the string "TrustAll" to install a trust manager that trusts all. + */ + public void setTrustManagerFactoryAlgorithm(String algorithm) + { + checkNotStarted(); + + _trustManagerFactoryAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return Path to file that contains Certificate Revocation List + */ + public String getCrlPath() + { + return _crlPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param crlPath + * Path to file that contains Certificate Revocation List + */ + public void setCrlPath(String crlPath) + { + checkNotStarted(); + + _crlPath = crlPath; + } + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public int getMaxCertPathLength() + { + return _maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxCertPathLength + * maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public void setMaxCertPathLength(int maxCertPathLength) + { + checkNotStarted(); + + _maxCertPathLength = maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSLContext + */ + public SSLContext getSslContext() + { + if (!isStarted()) + throw new IllegalStateException(getState()); + return _context; + } + + /* ------------------------------------------------------------ */ + /** + * @param sslContext + * Set a preconfigured SSLContext + */ + public void setSslContext(SSLContext sslContext) + { + checkNotStarted(); + + _context = sslContext; + } + + /* ------------------------------------------------------------ */ + /** + * Override this method to provide alternate way to load a keystore. + * + * @return the key store instance + * @throws Exception + */ + protected KeyStore loadKeyStore() throws Exception + { + return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream, + _keyStorePath, _keyStoreType, _keyStoreProvider, + _keyStorePassword==null? null: _keyStorePassword.toString()); + } + + /* ------------------------------------------------------------ */ + /** + * Override this method to provide alternate way to load a truststore. + * + * @return the key store instance + * @throws Exception + */ + protected KeyStore loadTrustStore() throws Exception + { + return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream, + _trustStorePath, _trustStoreType, _trustStoreProvider, + _trustStorePassword==null? null: _trustStorePassword.toString()); + } + + /* ------------------------------------------------------------ */ + /** + * Loads keystore using an input stream or a file path in the same + * order of precedence. + * + * Required for integrations to be able to override the mechanism + * used to load a keystore in order to provide their own implementation. + * + * @param storeStream keystore input stream + * @param storePath path of keystore file + * @param storeType keystore type + * @param storeProvider keystore provider + * @param storePassword keystore password + * @return created keystore + * @throws Exception + * + * @deprecated + */ + @Deprecated + protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception + { + return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword); + } + + /* ------------------------------------------------------------ */ + /** + * Loads certificate revocation list (CRL) from a file. + * + * Required for integrations to be able to override the mechanism used to + * load CRL in order to provide their own implementation. + * + * @param crlPath path of certificate revocation list file + * @return Collection of CRL's + * @throws Exception + */ + protected Collection loadCRL(String crlPath) throws Exception + { + return CertificateUtils.loadCRL(crlPath); + } + + /* ------------------------------------------------------------ */ + protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception + { + KeyManager[] managers = null; + + if (keyStore != null) + { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm); + keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray()); + managers = keyManagerFactory.getKeyManagers(); + + if (_certAlias != null) + { + for (int idx = 0; idx < managers.length; idx++) + { + if (managers[idx] instanceof X509KeyManager) + { + managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]); + } + } + } + } + + return managers; + } + + /* ------------------------------------------------------------ */ + protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection crls) throws Exception + { + TrustManager[] managers = null; + if (trustStore != null) + { + // Revocation checking is only supported for PKIX algorithm + if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX")) + { + PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector()); + + // Set maximum certification path length + pbParams.setMaxPathLength(_maxCertPathLength); + + // Make sure revocation checking is enabled + pbParams.setRevocationEnabled(true); + + if (crls != null && !crls.isEmpty()) + { + pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls))); + } + + if (_enableCRLDP) + { + // Enable Certificate Revocation List Distribution Points (CRLDP) support + System.setProperty("com.sun.security.enableCRLDP","true"); + } + + if (_enableOCSP) + { + // Enable On-Line Certificate Status Protocol (OCSP) support + Security.setProperty("ocsp.enable","true"); + + if (_ocspResponderURL != null) + { + // Override location of OCSP Responder + Security.setProperty("ocsp.responderURL", _ocspResponderURL); + } + } + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); + trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams)); + + managers = trustManagerFactory.getTrustManagers(); + } + else + { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); + trustManagerFactory.init(trustStore); + + managers = trustManagerFactory.getTrustManagers(); + } + } + + return managers; + } + + /* ------------------------------------------------------------ */ + /** + * Check KeyStore Configuration. Ensures that if keystore has been + * configured but there's no truststore, that keystore is + * used as truststore. + * @throws IllegalStateException if SslContextFactory configuration can't be used. + */ + public void checkKeyStore() + { + if (_context != null) + return; //nothing to check if using preconfigured context + + + if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null) + throw new IllegalStateException("SSL doesn't have a valid keystore"); + + // if the keystore has been configured but there is no + // truststore configured, use the keystore as the truststore + if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null) + { + _trustStore = _keyStore; + _trustStorePath = _keyStorePath; + _trustStoreInputStream = _keyStoreInputStream; + _trustStoreType = _keyStoreType; + _trustStoreProvider = _keyStoreProvider; + _trustStorePassword = _keyStorePassword; + _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm; + } + + // It's the same stream we cannot read it twice, so read it once in memory + if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IO.copy(_keyStoreInputStream, baos); + _keyStoreInputStream.close(); + + _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); + _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); + } + catch (Exception ex) + { + throw new IllegalStateException(ex); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Select cipher suites to be used by the connector + * based on configured inclusion and exclusion lists + * as well as enabled and supported cipher suite lists. + * @param enabledCipherSuites Array of enabled cipher suites + * @param supportedCipherSuites Array of supported cipher suites + * @return Array of cipher suites to enable + */ + public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols) + { + Set selected_protocols = new HashSet(); + + // Set the starting protocols - either from the included or enabled list + if (_includeProtocols!=null) + { + // Use only the supported included protocols + for (String protocol : supportedProtocols) + if (_includeProtocols.contains(protocol)) + selected_protocols.add(protocol); + } + else + selected_protocols.addAll(Arrays.asList(enabledProtocols)); + + + // Remove any excluded protocols + if (_excludeProtocols != null) + selected_protocols.removeAll(_excludeProtocols); + + return selected_protocols.toArray(new String[selected_protocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Select cipher suites to be used by the connector + * based on configured inclusion and exclusion lists + * as well as enabled and supported cipher suite lists. + * @param enabledCipherSuites Array of enabled cipher suites + * @param supportedCipherSuites Array of supported cipher suites + * @return Array of cipher suites to enable + */ + public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) + { + Set selected_ciphers = new HashSet(); + + // Set the starting ciphers - either from the included or enabled list + if (_includeCipherSuites!=null) + { + // Use only the supported included ciphers + for (String cipherSuite : supportedCipherSuites) + if (_includeCipherSuites.contains(cipherSuite)) + selected_ciphers.add(cipherSuite); + } + else + selected_ciphers.addAll(Arrays.asList(enabledCipherSuites)); + + + // Remove any excluded ciphers + if (_excludeCipherSuites != null) + selected_ciphers.removeAll(_excludeCipherSuites); + return selected_ciphers.toArray(new String[selected_ciphers.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Check if the lifecycle has been started and throw runtime exception + */ + protected void checkNotStarted() + { + if (isStarted()) + throw new IllegalStateException("Cannot modify configuration when "+getState()); + } + + /* ------------------------------------------------------------ */ + /** + * @return true if CRL Distribution Points support is enabled + */ + public boolean isEnableCRLDP() + { + return _enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** Enables CRL Distribution Points Support + * @param enableCRLDP true - turn on, false - turns off + */ + public void setEnableCRLDP(boolean enableCRLDP) + { + checkNotStarted(); + + _enableCRLDP = enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if On-Line Certificate Status Protocol support is enabled + */ + public boolean isEnableOCSP() + { + return _enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** Enables On-Line Certificate Status Protocol support + * @param enableOCSP true - turn on, false - turn off + */ + public void setEnableOCSP(boolean enableOCSP) + { + checkNotStarted(); + + _enableOCSP = enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** + * @return Location of the OCSP Responder + */ + public String getOcspResponderURL() + { + return _ocspResponderURL; + } + + /* ------------------------------------------------------------ */ + /** Set the location of the OCSP Responder. + * @param ocspResponderURL location of the OCSP Responder + */ + public void setOcspResponderURL(String ocspResponderURL) + { + checkNotStarted(); + + _ocspResponderURL = ocspResponderURL; + } + + /* ------------------------------------------------------------ */ + /** Set the key store. + * @param keyStore the key store to set + */ + public void setKeyStore(KeyStore keyStore) + { + checkNotStarted(); + + _keyStore = keyStore; + } + + /* ------------------------------------------------------------ */ + /** Set the trust store. + * @param trustStore the trust store to set + */ + public void setTrustStore(KeyStore trustStore) + { + checkNotStarted(); + + _trustStore = trustStore; + } + + /* ------------------------------------------------------------ */ + /** Set the key store resource. + * @param resource the key store resource to set + */ + public void setKeyStoreResource(Resource resource) + { + checkNotStarted(); + + try + { + _keyStoreInputStream = resource.getInputStream(); + } + catch (IOException e) + { + throw new InvalidParameterException("Unable to get resource "+ + "input stream for resource "+resource.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** Set the trust store resource. + * @param resource the trust store resource to set + */ + public void setTrustStoreResource(Resource resource) + { + checkNotStarted(); + + try + { + _trustStoreInputStream = resource.getInputStream(); + } + catch (IOException e) + { + throw new InvalidParameterException("Unable to get resource "+ + "input stream for resource "+resource.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL Session caching is enabled + */ + public boolean isSessionCachingEnabled() + { + return _sessionCachingEnabled; + } + + /* ------------------------------------------------------------ */ + /** Set the flag to enable SSL Session caching. + * @param enableSessionCaching the value of the flag + */ + public void setSessionCachingEnabled(boolean enableSessionCaching) + { + _sessionCachingEnabled = enableSessionCaching; + } + + /* ------------------------------------------------------------ */ + /** Get SSL session cache size. + * @return SSL session cache size + */ + public int getSslSessionCacheSize() + { + return _sslSessionCacheSize; + } + + /* ------------------------------------------------------------ */ + /** SEt SSL session cache size. + * @param sslSessionCacheSize SSL session cache size to set + */ + public void setSslSessionCacheSize(int sslSessionCacheSize) + { + _sslSessionCacheSize = sslSessionCacheSize; + } + + /* ------------------------------------------------------------ */ + /** Get SSL session timeout. + * @return SSL session timeout + */ + public int getSslSessionTimeout() + { + return _sslSessionTimeout; + } + + /* ------------------------------------------------------------ */ + /** Set SSL session timeout. + * @param sslSessionTimeout SSL session timeout to set + */ + public void setSslSessionTimeout(int sslSessionTimeout) + { + _sslSessionTimeout = sslSessionTimeout; + } + + + /* ------------------------------------------------------------ */ + public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException + { + SSLServerSocketFactory factory = _context.getServerSocketFactory(); + + SSLServerSocket socket = + (SSLServerSocket) (host==null ? + factory.createServerSocket(port,backlog): + factory.createServerSocket(port,backlog,InetAddress.getByName(host))); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLSocket newSslSocket() throws IOException + { + SSLSocketFactory factory = _context.getSocketFactory(); + + SSLSocket socket = (SSLSocket)factory.createSocket(); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine(String host,int port) + { + SSLEngine sslEngine=isSessionCachingEnabled() + ?_context.createSSLEngine(host, port) + :_context.createSSLEngine(); + + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine() + { + SSLEngine sslEngine=_context.createSSLEngine(); + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public void customize(SSLEngine sslEngine) + { + if (getWantClientAuth()) + sslEngine.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + sslEngine.setNeedClientAuth(getNeedClientAuth()); + + sslEngine.setEnabledCipherSuites(selectCipherSuites( + sslEngine.getEnabledCipherSuites(), + sslEngine.getSupportedCipherSuites())); + + sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols())); + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java index cd92f7ff9d9..0934e7c24a1 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util.thread; @@ -28,9 +28,9 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ -/** +/** * Jetty ThreadPool using java 5 ThreadPoolExecutor - * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and + * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and * {@link LifeCycle} interfaces so that it may be used by the Jetty org.eclipse.jetty.server.Server */ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle @@ -41,67 +41,83 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, /* ------------------------------------------------------------ */ public ExecutorThreadPool(ExecutorService executor) { - _executor=executor; + _executor = executor; } - + /* ------------------------------------------------------------ */ - /** constructor. + /** * Wraps an {@link ThreadPoolExecutor}. - * Core size is 32, max pool size is 256, pool thread timeout after 60 seconds and + * Max pool size is 256, pool thread timeout after 60 seconds and * an unbounded {@link LinkedBlockingQueue} is used for the job queue; */ public ExecutorThreadPool() { - this(new ThreadPoolExecutor(32,256,60,TimeUnit.SECONDS,new LinkedBlockingQueue())); + // Using an unbounded queue makes the maxThreads parameter useless + // Refer to ThreadPoolExecutor javadocs for details + this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue())); } - + /* ------------------------------------------------------------ */ - /** constructor. + /** * Wraps an {@link ThreadPoolExecutor}. - * Core size is 32, max pool size is 256, pool thread timeout after 60 seconds - * @param queueSize if -1, an unbounded {@link LinkedBlockingQueue} is used, if 0 then a - * {@link SynchronousQueue} is used, other a {@link ArrayBlockingQueue} of the given size is used. + * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0. + * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a + * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size. */ public ExecutorThreadPool(int queueSize) { - this(new ThreadPoolExecutor(32,256,60,TimeUnit.SECONDS, - queueSize<0?new LinkedBlockingQueue() - : (queueSize==0?new SynchronousQueue() - :new ArrayBlockingQueue(queueSize)))); + this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()) : + queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue()) : + new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(queueSize))); } /* ------------------------------------------------------------ */ - /** constructor. + /** * Wraps an {@link ThreadPoolExecutor} using * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue; + * @param corePoolSize must be equal to maximumPoolSize + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle, in milliseconds */ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime) { - this(new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())); + this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS); } /* ------------------------------------------------------------ */ - /** constructor. + /** * Wraps an {@link ThreadPoolExecutor} using - * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue; + * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue. + * @param corePoolSize must be equal to maximumPoolSize + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle + * @param unit the unit for the keepAliveTime */ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { - this(new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,new LinkedBlockingQueue())); + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue()); } /* ------------------------------------------------------------ */ + + /** + * Wraps an {@link ThreadPoolExecutor} + * @param corePoolSize the number of threads to keep in the pool, even if they are idle + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle + * @param unit the unit for the keepAliveTime + * @param workQueue the queue to use for holding tasks before they are executed + */ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { - this(new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue)); + this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)); } - /* ------------------------------------------------------------ */ public boolean dispatch(Runnable job) { try - { + { _executor.execute(job); return true; } @@ -118,7 +134,7 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, if (_executor instanceof ThreadPoolExecutor) { final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; - return tpe.getPoolSize() -tpe.getActiveCount(); + return tpe.getPoolSize() - tpe.getActiveCount(); } return -1; } @@ -139,8 +155,10 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, { if (_executor instanceof ThreadPoolExecutor) { - final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; - return tpe.getTaskCount()>=(tpe.getMaximumPoolSize()); + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + // getActiveCount() locks the thread pool, so execute it last + return tpe.getPoolSize() == tpe.getMaximumPoolSize() && + tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount(); } return false; } @@ -148,14 +166,7 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, /* ------------------------------------------------------------ */ public void join() throws InterruptedException { - _executor.awaitTermination(Long.MAX_VALUE,TimeUnit.MILLISECONDS); - } - - /* ------------------------------------------------------------ */ - @Override - protected void doStart() throws Exception - { - super.doStart(); + _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); } /* ------------------------------------------------------------ */ @@ -165,5 +176,4 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, super.doStop(); _executor.shutdownNow(); } - } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index 5c54999a032..be3f0ed4419 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== @@ -39,7 +39,7 @@ import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool; public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Executor, Dumpable { private static final Logger LOG = Log.getLogger(QueuedThreadPool.class); - + private final AtomicInteger _threadsStarted = new AtomicInteger(); private final AtomicInteger _threadsIdle = new AtomicInteger(); private final AtomicLong _lastShrink = new AtomicLong(); @@ -63,7 +63,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { _name="qtp"+super.hashCode(); } - + /* ------------------------------------------------------------------- */ /** Construct */ @@ -72,7 +72,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo this(); setMaxThreads(maxThreads); } - + /* ------------------------------------------------------------------- */ /** Construct */ @@ -82,8 +82,8 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo _jobs=jobQ; _jobs.clear(); } - - + + /* ------------------------------------------------------------ */ @Override protected void doStart() throws Exception @@ -100,8 +100,8 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo int threads=_threadsStarted.get(); while (isRunning() && threads<_minThreads) { - startThread(threads); - threads=_threadsStarted.get(); + startThread(threads); + threads=_threadsStarted.get(); } } @@ -115,7 +115,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo // let jobs complete naturally for a while while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2)) Thread.sleep(1); - + // kill queued jobs and flush out idle jobs _jobs.clear(); Runnable noop = new Runnable(){public void run(){}}; @@ -127,7 +127,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo if (_threadsStarted.get()>0) for (Thread thread : _threads) thread.interrupt(); - + // wait for remaining threads to die while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime) { @@ -138,7 +138,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo if (size>0) { LOG.warn(size+" threads could not be stopped"); - + if (size==1 || LOG.isDebugEnabled()) { for (Thread unstopped : _threads) @@ -151,7 +151,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } } } - + synchronized (_joinLock) { _joinLock.notifyAll(); @@ -159,14 +159,14 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } /* ------------------------------------------------------------ */ - /** + /** * Delegated to the named or anonymous Pool. */ public void setDaemon(boolean daemon) { _daemon=daemon; } - + /* ------------------------------------------------------------ */ /** Set the maximum thread idle time. * Threads that are idle for longer than this period may be @@ -214,17 +214,17 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo if (_minThreads>_maxThreads) _maxThreads=_minThreads; - + int threads=_threadsStarted.get(); while (isStarted() && threads<_minThreads) { - startThread(threads); - threads=_threadsStarted.get(); + startThread(threads); + threads=_threadsStarted.get(); } } /* ------------------------------------------------------------ */ - /** + /** * @param name Name of the BoundedThreadPool to use when naming Threads. */ public void setName(String name) @@ -242,7 +242,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { _priority=priority; } - + /* ------------------------------------------------------------ */ /** * @return maximum queue size @@ -251,7 +251,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { return _maxQueued; } - + /* ------------------------------------------------------------ */ /** * @param max job queue size @@ -262,7 +262,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo throw new IllegalStateException("started"); _maxQueued=max; } - + /* ------------------------------------------------------------ */ /** Get the maximum thread idle time. * Delegated to the named or anonymous Pool. @@ -272,8 +272,8 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo public int getMaxIdleTimeMs() { return _maxIdleTimeMs; - } - + } + /* ------------------------------------------------------------ */ /** * @return maximum total time that stop() will wait for threads to die. @@ -282,7 +282,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { return _maxStopTime; } - + /* ------------------------------------------------------------ */ /** Set the maximum number of threads. * Delegated to the named or anonymous Pool. @@ -306,7 +306,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } /* ------------------------------------------------------------ */ - /** + /** * @return The name of the BoundedThreadPool. */ public String getName() @@ -322,9 +322,9 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { return _priority; } - + /* ------------------------------------------------------------ */ - /** + /** * Delegated to the named or anonymous Pool. */ public boolean isDaemon() @@ -365,7 +365,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } return false; } - + /* ------------------------------------------------------------ */ public void execute(Runnable job) { @@ -378,13 +378,13 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo * Blocks until the thread pool is {@link LifeCycle#stop stopped}. */ public void join() throws InterruptedException - { + { synchronized (_joinLock) { while (isRunning()) _joinLock.wait(); } - + while (isStopping()) Thread.sleep(1); } @@ -406,7 +406,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { return _threadsIdle.get(); } - + /* ------------------------------------------------------------ */ /** * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs @@ -422,7 +422,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo final int next=threads+1; if (!_threadsStarted.compareAndSet(threads,next)) return false; - + boolean started=false; try { @@ -431,7 +431,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo thread.setPriority(_priority); thread.setName(_name+"-"+thread.getId()); _threads.add(thread); - + thread.start(); started=true; } @@ -442,7 +442,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } return started; } - + /* ------------------------------------------------------------ */ protected Thread newThread(Runnable runnable) { @@ -458,8 +458,8 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo /* ------------------------------------------------------------ */ public void dump(Appendable out, String indent) throws IOException - { - List dump = new ArrayList(getMaxThreads()); + { + List dump = new ArrayList(getMaxThreads()); for (final Thread thread: _threads) { final StackTraceElement[] trace=thread.getStackTrace(); @@ -473,7 +473,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } } final boolean idle=inIdleJobPoll; - + if (_detailedDump) { dump.add(new Dumpable() @@ -484,7 +484,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo if (!idle) AggregateLifeCycle.dump(out,indent,Arrays.asList(trace)); } - + public String dump() { return null; @@ -499,10 +499,10 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo out.append(String.valueOf(this)).append("\n"); AggregateLifeCycle.dump(out,indent,dump); - + } - - + + /* ------------------------------------------------------------ */ @Override public String toString() @@ -515,7 +515,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo { return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS); } - + /* ------------------------------------------------------------ */ private Runnable _runnable = new Runnable() { @@ -530,10 +530,10 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo // Job loop while (job!=null && isRunning()) { - job.run(); + runJob(job); job=_jobs.poll(); } - + // Idle loop try { @@ -585,7 +585,28 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } } }; - + + /* ------------------------------------------------------------ */ + /** + *

    Runs the given job in the {@link Thread#currentThread() current thread}.

    + *

    Subclasses may override to perform pre/post actions before/after the job is run.

    + * + * @param job the job to run + */ + protected void runJob(Runnable job) + { + job.run(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the job queue + */ + protected BlockingQueue getQueue() + { + return _jobs; + } + /* ------------------------------------------------------------ */ /** * @param id The thread ID to stop. @@ -605,7 +626,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } return false; } - + /* ------------------------------------------------------------ */ /** * @param id The thread ID to interrupt. @@ -623,7 +644,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } return false; } - + /* ------------------------------------------------------------ */ /** * @param id The thread ID to interrupt. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java index 96c9c2527e8..d0bbce9fe06 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java @@ -122,7 +122,7 @@ public class ShutdownThread extends Thread try { lifeCycle.stop(); - LOG.debug("Stopped " + lifeCycle); + LOG.debug("Stopped {}",lifeCycle); } catch (Exception ex) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java index 5cfdf20d87d..151338e5b21 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java @@ -13,7 +13,11 @@ package org.eclipse.jetty.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.net.URI; import java.util.ArrayList; diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java index 8a85c295627..a7992b533ec 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.Scanner.Notification; import org.junit.AfterClass; @@ -25,10 +27,8 @@ public class ScannerTest @BeforeClass public static void setUpBeforeClass() throws Exception { - _directory = File.createTempFile("scan",""); - _directory.delete(); - _directory.mkdir(); - _directory.deleteOnExit(); + _directory = MavenTestingUtils.getTargetTestingDir(ScannerTest.class.getSimpleName()); + FS.ensureEmpty(_directory); _scanner = new Scanner(); _scanner.addScanDir(_directory); @@ -88,7 +88,7 @@ public class ScannerTest public void testAddedChangeRemove() throws Exception { // TODO needs to be further investigated - Assume.assumeTrue(!OS.IS_WINDOWS && !OS.IS_OSX); + Assume.assumeTrue(!OS.IS_WINDOWS); touch("a0"); @@ -96,7 +96,7 @@ public class ScannerTest _scanner.scan(); _scanner.scan(); Event event = _queue.poll(); - Assert.assertTrue(event!=null); + Assert.assertNotNull("Event should not be null", event); Assert.assertEquals(_directory+"/a0",event._filename); Assert.assertEquals(Notification.ADDED,event._notification); @@ -197,7 +197,7 @@ public class ScannerTest public void testSizeChange() throws Exception { // TODO needs to be further investigated - Assume.assumeTrue(!OS.IS_WINDOWS && !OS.IS_OSX); + Assume.assumeTrue(!OS.IS_WINDOWS); touch("tsc0"); _scanner.scan(); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java index 30f70621404..42bba2b05bc 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; -import org.junit.Ignore; import org.junit.Test; @@ -179,7 +178,7 @@ public class URLEncodedTest { ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0])); MultiMap m = new MultiMap(); - UrlEncoded.decodeTo(in, m, charsets[i][1], -1); + UrlEncoded.decodeTo(in, m, charsets[i][1], -1,-1); System.err.println(m); assertEquals(i+" stream length",4,m.size()); assertEquals(i+" stream name\\n","value 0",m.getString("name\n")); @@ -193,7 +192,7 @@ public class URLEncodedTest { ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes()); MultiMap m2 = new MultiMap(); - UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1); + UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1,-1); assertEquals("stream length",1,m2.size()); assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name")); } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java index 555ea45bbca..6af4c080781 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; @@ -16,72 +16,86 @@ package org.eclipse.jetty.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.junit.Test; +import java.io.UnsupportedEncodingException; +import org.junit.Test; public class Utf8StringBufferTest { - public void testUtfStringBuffer() - throws Exception + @Test + public void testUtfStringBuffer() throws Exception { - String source="abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; + String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; byte[] bytes = source.getBytes(StringUtil.__UTF8); Utf8StringBuffer buffer = new Utf8StringBuffer(); - for (int i=0;i=0); - } - } - - @Test - public void testLong() - throws Exception - { - String source="abcXX"; - byte[] bytes = source.getBytes(StringUtil.__UTF8); - bytes[3]=(byte)0xc0; - bytes[4]=(byte)0x00; + String source = "\uD842\uDF9F"; + byte[] bytes = source.getBytes("UTF-8"); - Utf8StringBuffer buffer = new Utf8StringBuffer(); - for (int i=0;i=0); + assertTrue(true); } + assertEquals("abc\ufffd",buffer.toString()); } - + @Test - public void testLong() - throws Exception + public void testUTF32codes() throws Exception { - String source="abcXX"; - byte[] bytes = source.getBytes(StringUtil.__UTF8); - bytes[3]=(byte)0xc0; - bytes[4]=(byte)0x00; + String source = "\uD842\uDF9F"; + byte[] bytes = source.getBytes("UTF-8"); - Utf8StringBuilder buffer = new Utf8StringBuilder(); - for (int i=0;i bz = (Map)br.get("baz"); Map f = (Map)bz.get("foo"); - + assertTrue(f != null); Object[] bazs = (Object[])br.get("bazs"); assertTrue(bazs.length==2); assertEquals(((Map)bazs[0]).get("message"), "baz0"); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java index 4f700dd0c58..58d8257b52a 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java @@ -203,7 +203,7 @@ public class JSONTest public void testZeroByte() { String withzero="\u0000"; - String json = JSON.toString(withzero); + JSON.toString(withzero); } /* ------------------------------------------------------------ */ diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java index f45463b5196..7a7a915ccf6 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java @@ -135,7 +135,7 @@ public class LifeCycleListenerTest lifecycle.stop(); assertFalse("The stopping event occurred", listener.stopping); } - private class TestLifeCycle extends AbstractLifeCycle + private static class TestLifeCycle extends AbstractLifeCycle { Exception cause; diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/CapturingJULHandler.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/CapturingJULHandler.java new file mode 100644 index 00000000000..1ec646e669c --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/CapturingJULHandler.java @@ -0,0 +1,69 @@ +package org.eclipse.jetty.util.log; + +import static org.hamcrest.Matchers.containsString; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +import org.eclipse.jetty.util.IO; +import org.junit.Assert; + +public class CapturingJULHandler extends Handler +{ + private static final String LN = System.getProperty("line.separator"); + private StringBuilder output = new StringBuilder(); + + @Override + public void publish(LogRecord record) + { + StringBuilder buf = new StringBuilder(); + buf.append(record.getLevel().getName()).append("|"); + buf.append(record.getLoggerName()).append("|"); + buf.append(record.getMessage()); + + output.append(buf); + if (record.getMessage().length() > 0) + { + output.append(LN); + } + + if (record.getThrown() != null) + { + StringWriter sw = new StringWriter(128); + PrintWriter capture = new PrintWriter(sw); + record.getThrown().printStackTrace(capture); + capture.flush(); + output.append(sw.toString()); + IO.close(capture); + } + } + + public void clear() + { + output.setLength(0); + } + + @Override + public void flush() + { + /* do nothing */ + } + + @Override + public void close() throws SecurityException + { + /* do nothing */ + } + + public void dump() + { + System.out.println(output); + } + + public void assertContainsLine(String line) + { + Assert.assertThat(output.toString(),containsString(line)); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java new file mode 100644 index 00000000000..244b1e1306e --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java @@ -0,0 +1,224 @@ +package org.eclipse.jetty.util.log; + +import static org.hamcrest.Matchers.is; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class JavaUtilLogTest +{ + private static Handler[] originalHandlers; + private static CapturingJULHandler jul; + + @BeforeClass + public static void setJUL() + { + LogManager lmgr = LogManager.getLogManager(); + java.util.logging.Logger root = lmgr.getLogger(""); + // Remember original handlers + originalHandlers = root.getHandlers(); + // Remove original handlers + for (Handler existing : originalHandlers) + { + root.removeHandler(existing); + } + // Set test/capturing handler + jul = new CapturingJULHandler(); + root.addHandler(jul); + } + + @AfterClass + public static void restoreJUL() + { + LogManager lmgr = LogManager.getLogManager(); + java.util.logging.Logger root = lmgr.getLogger(""); + // Remove test handlers + for (Handler existing : root.getHandlers()) + { + root.removeHandler(existing); + } + // Restore original handlers + for (Handler original : originalHandlers) + { + root.addHandler(original); + } + } + + @Test + public void testNamedLogger() + { + jul.clear(); + JavaUtilLog log = new JavaUtilLog("test"); + log.info("Info test"); + + jul.assertContainsLine("INFO|test|Info test"); + + JavaUtilLog loglong = new JavaUtilLog("test.a.long.name"); + loglong.info("Long test"); + + jul.assertContainsLine("INFO|test.a.long.name|Long test"); + } + + @Test + public void testDebugOutput() + { + jul.clear(); + + // Common Throwable (for test) + Throwable th = new Throwable("Message"); + + // Capture raw string form + StringWriter tout = new StringWriter(); + th.printStackTrace(new PrintWriter(tout)); + String ths = tout.toString(); + + // Tests + JavaUtilLog log = new JavaUtilLog("test.de.bug"); + setJulLevel("test.de.bug",Level.FINE); + + log.debug("Simple debug"); + log.debug("Debug with {} parameter",1); + log.debug("Debug with {} {} parameters", 2, "spiffy"); + log.debug("Debug with throwable", th); + log.debug(th); + + // jul.dump(); + + jul.assertContainsLine("FINE|test.de.bug|Simple debug"); + jul.assertContainsLine("FINE|test.de.bug|Debug with 1 parameter"); + jul.assertContainsLine("FINE|test.de.bug|Debug with 2 spiffy parameters"); + jul.assertContainsLine("FINE|test.de.bug|Debug with throwable"); + jul.assertContainsLine(ths); + } + + @Test + public void testInfoOutput() + { + jul.clear(); + + // Common Throwable (for test) + Throwable th = new Throwable("Message"); + + // Capture raw string form + StringWriter tout = new StringWriter(); + th.printStackTrace(new PrintWriter(tout)); + String ths = tout.toString(); + + // Tests + JavaUtilLog log = new JavaUtilLog("test.in.fo"); + setJulLevel("test.in.fo",Level.INFO); + + log.info("Simple info"); + log.info("Info with {} parameter",1); + log.info("Info with {} {} parameters", 2, "spiffy"); + log.info("Info with throwable", th); + log.info(th); + + // jul.dump(); + + jul.assertContainsLine("INFO|test.in.fo|Simple info"); + jul.assertContainsLine("INFO|test.in.fo|Info with 1 parameter"); + jul.assertContainsLine("INFO|test.in.fo|Info with 2 spiffy parameters"); + jul.assertContainsLine("INFO|test.in.fo|Info with throwable"); + jul.assertContainsLine(ths); + } + + @Test + public void testWarnOutput() + { + jul.clear(); + + // Common Throwable (for test) + Throwable th = new Throwable("Message"); + + // Capture raw string form + StringWriter tout = new StringWriter(); + th.printStackTrace(new PrintWriter(tout)); + String ths = tout.toString(); + + // Tests + JavaUtilLog log = new JavaUtilLog("test.wa.rn"); + setJulLevel("test.wa.rn",Level.WARNING); + + log.warn("Simple warn"); + log.warn("Warn with {} parameter",1); + log.warn("Warn with {} {} parameters", 2, "spiffy"); + log.warn("Warn with throwable", th); + log.warn(th); + + // jul.dump(); + + jul.assertContainsLine("WARNING|test.wa.rn|Simple warn"); + jul.assertContainsLine("WARNING|test.wa.rn|Warn with 1 parameter"); + jul.assertContainsLine("WARNING|test.wa.rn|Warn with 2 spiffy parameters"); + jul.assertContainsLine("WARNING|test.wa.rn|Warn with throwable"); + jul.assertContainsLine(ths); + } + + @Test + public void testFormattingWithNulls() + { + jul.clear(); + + JavaUtilLog log = new JavaUtilLog("test.nu.ll"); + setJulLevel("test.nu.ll",Level.INFO); + + log.info("Testing info(msg,null,null) - {} {}","arg0","arg1"); + log.info("Testing info(msg,null,null) - {}/{}",null,null); + log.info("Testing info(msg,null,null) > {}",null,null); + log.info("Testing info(msg,null,null)",null,null); + log.info(null,"Testing","info(null,arg0,arg1)"); + log.info(null,null,null); + + jul.dump(); + + jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) - null/null"); + jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) > null null"); + jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) null null"); + jul.assertContainsLine("INFO|test.nu.ll|null Testing info(null,arg0,arg1)"); + jul.assertContainsLine("INFO|test.nu.ll|null null null"); + } + + @Test + public void testIsDebugEnabled() { + JavaUtilLog log = new JavaUtilLog("test.legacy"); + + setJulLevel("test.legacy",Level.ALL); + Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true)); + + setJulLevel("test.legacy",Level.FINEST); + Assert.assertThat("log.level(finest).isDebugEnabled", log.isDebugEnabled(), is(true)); + + setJulLevel("test.legacy",Level.FINER); + Assert.assertThat("log.level(finer).isDebugEnabled", log.isDebugEnabled(), is(true)); + + setJulLevel("test.legacy",Level.FINE); + Assert.assertThat("log.level(fine).isDebugEnabled", log.isDebugEnabled(), is(true)); + + setJulLevel("test.legacy",Level.INFO); + Assert.assertThat("log.level(info).isDebugEnabled", log.isDebugEnabled(), is(false)); + + setJulLevel("test.legacy",Level.WARNING); + Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false)); + + log.setDebugEnabled(true); + Assert.assertThat("log.isDebugEnabled", log.isDebugEnabled(), is(true)); + + log.setDebugEnabled(false); + Assert.assertThat("log.isDebugEnabled", log.isDebugEnabled(), is(false)); + } + + private void setJulLevel(String name, Level lvl) + { + java.util.logging.Logger log = java.util.logging.Logger.getLogger(name); + log.setLevel(lvl); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java index 3495cffb99e..e5322ca63b9 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java @@ -13,157 +13,71 @@ package org.eclipse.jetty.util.log; -import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import junit.framework.Assert; +import static org.hamcrest.Matchers.is; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; - public class LogTest { - static PrintStream _orig= System.err; - static ByteArrayOutputStream _out = new ByteArrayOutputStream(); - static PrintStream _pout = new PrintStream(_out); - + private static Logger originalLogger; + @SuppressWarnings("deprecation") @BeforeClass - public static void setUp() + public static void rememberOriginalLogger() { - System.setErr(_pout); + originalLogger = Log.getLog(); } - + @AfterClass - public static void tearDown() + public static void restoreOriginalLogger() { - System.setErr(_orig); - } - - private void logNotContains(String text) - { - _pout.flush(); - String err = _out.toString(); - _out.reset(); - - if (err.indexOf(text)<0) - return; - - _orig.println("FAIL '"+text+"' in '"+err+"'"); - - assertTrue(false); - } - - private void logContains(String text) - { - _pout.flush(); - String err = _out.toString(); - _out.reset(); - - err = err.replaceAll("\r\n","\n"); - text = text.replaceAll("\r\n","\n"); - - if (err.indexOf(text)>=0) - return; - - _orig.println("FAIL '"+text+"' not in '"+err+"'"); - assertTrue(false); + Log.setLog(originalLogger); } @Test - public void testStdErrLogFormat() + public void testDefaultLogging() { - StdErrLog log = new StdErrLog(LogTest.class.getName()); + Logger log = Log.getLogger(LogTest.class); + log.info("Test default logging"); + } - log.info("testing:{},{}","test","format"); - logContains("INFO:oejul.LogTest:testing:test,format"); - - log.info("testing:{}","test","format"); - logContains("INFO:oejul.LogTest:testing:test format"); - - log.info("testing","test","format"); - logContains("INFO:oejul.LogTest:testing test format"); - - log.info("testing:{},{}","test",null); - logContains("INFO:oejul.LogTest:testing:test,null"); - - log.info("testing {} {}",null,null); - logContains("INFO:oejul.LogTest:testing null null"); - - log.info("testing:{}",null,null); - logContains("INFO:oejul.LogTest:testing:null"); - - log.info("testing",null,null); - logContains("INFO:oejul.LogTest:testing"); + // @Test + public void testNamedLogNamed_StdErrLog() + { + Log.setLog(new StdErrLog()); + + assertNamedLogging(Red.class); + assertNamedLogging(Blue.class); + assertNamedLogging(Green.class); } @Test - public void testStdErrLogDebug() + public void testNamedLogNamed_JUL() { - StdErrLog log = new StdErrLog("xxx"); - - log.setDebugEnabled(true); - log.debug("testing {} {}","test","debug"); - logContains("DBUG:xxx:testing test debug"); - - log.info("testing {} {}","test","info"); - logContains("INFO:xxx:testing test info"); - - log.warn("testing {} {}","test","warn"); - logContains("WARN:xxx:testing test warn"); - - log.setDebugEnabled(false); - log.debug("YOU SHOULD NOT SEE THIS!",null,null); - logNotContains("YOU SHOULD NOT SEE THIS!"); + Log.setLog(new JavaUtilLog()); + + assertNamedLogging(Red.class); + assertNamedLogging(Blue.class); + assertNamedLogging(Green.class); } - + @Test - public void testStdErrLogName() + public void testNamedLogNamed_Slf4J() throws Exception { - StdErrLog log = new StdErrLog("test"); - log.setPrintLongNames(true); - Assert.assertEquals("test",log.getName()); - - Logger next=log.getLogger("next"); - - Assert.assertEquals("test.next",next.getName()); - - next.info("testing {} {}","next","info"); - logContains(":test.next:testing next info"); - + Log.setLog(new Slf4jLog()); + + assertNamedLogging(Red.class); + assertNamedLogging(Blue.class); + assertNamedLogging(Green.class); } - - @Test - public void testStdErrThrowable() + + @SuppressWarnings("deprecation") + private void assertNamedLogging(Class clazz) { - Throwable th = new Throwable("Message"); - - th.printStackTrace(); - _pout.flush(); - String ths = _out.toString(); - _out.reset(); - - - StdErrLog log = new StdErrLog("test"); - log.warn("ex",th); - - logContains(ths); - - th = new Throwable("Message with \033 escape"); - - log.warn("ex",th); - logNotContains("Message with \033 escape"); - log.info(th.toString()); - logNotContains("Message with \033 escape"); - - log.warn("ex",th); - logContains("Message with ? escape"); - log.info(th.toString()); - logContains("Message with ? escape"); - + Logger lc = Log.getLogger(clazz); + Assert.assertThat("Named logging (impl=" + Log.getLog().getClass().getName() + ")",lc.getName(),is(clazz.getName())); } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/NamedLogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/NamedLogTest.java index 67869ce1a24..dc1a86e84a1 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/NamedLogTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/NamedLogTest.java @@ -1,43 +1,24 @@ package org.eclipse.jetty.util.log; -import static org.hamcrest.Matchers.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; public class NamedLogTest { - private PrintStream orig; - private ByteArrayOutputStream logstream; - private PrintStream perr; - private Logger origLogger; + private static Logger originalLogger; - @Before - public void setUp() + @SuppressWarnings("deprecation") + @BeforeClass + public static void rememberOriginalLogger() { - origLogger = Log.getRootLogger(); - - orig = System.err; - logstream = new ByteArrayOutputStream(); - perr = new PrintStream(logstream); - System.setErr(perr); - - StdErrLog logger = new StdErrLog(); - Log.setLog(logger); + originalLogger = Log.getLog(); } - @After - public void tearDown() + @AfterClass + public static void restoreOriginalLogger() { - System.out.println(logstream.toString()); - System.setErr(orig); - - Log.setLog(origLogger); + Log.setLog(originalLogger); } @Test @@ -46,29 +27,32 @@ public class NamedLogTest Red red = new Red(); Green green = new Green(); Blue blue = new Blue(); - - setLoggerOptions(Red.class); - setLoggerOptions(Green.class); - setLoggerOptions(Blue.class); + + StdErrCapture output = new StdErrCapture(); + + setLoggerOptions(Red.class,output); + setLoggerOptions(Green.class,output); + setLoggerOptions(Blue.class,output); red.generateLogs(); green.generateLogs(); blue.generateLogs(); - String rawlog = logstream.toString(); - - Assert.assertThat(rawlog,containsString(Red.class.getName())); - Assert.assertThat(rawlog,containsString(Green.class.getName())); - Assert.assertThat(rawlog,containsString(Blue.class.getName())); + output.assertContains(Red.class.getName()); + output.assertContains(Green.class.getName()); + output.assertContains(Blue.class.getName()); } - private void setLoggerOptions(Class clazz) + private void setLoggerOptions(Class clazz, StdErrCapture output) { Logger logger = Log.getLogger(clazz); logger.setDebugEnabled(true); - - if(logger instanceof StdErrLog) { - ((StdErrLog)logger).setPrintLongNames(true); + + if (logger instanceof StdErrLog) + { + StdErrLog sel = (StdErrLog)logger; + sel.setPrintLongNames(true); + output.capture(sel); } } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/Slf4jHelper.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/Slf4jHelper.java new file mode 100644 index 00000000000..10b2d02c205 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/Slf4jHelper.java @@ -0,0 +1,42 @@ +package org.eclipse.jetty.util.log; + +import java.io.File; +import java.io.FileFilter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.junit.Assume; + +public final class Slf4jHelper +{ + public static ClassLoader createTestClassLoader(ClassLoader parentClassLoader) throws MalformedURLException + { + File testJarDir = MavenTestingUtils.getTargetFile("test-jars"); + Assume.assumeTrue(testJarDir.exists()); // trigger @Ignore if dir not there + + File jarfiles[] = testJarDir.listFiles(new FileFilter() + { + public boolean accept(File path) + { + if (!path.isFile()) + { + return false; + } + return path.getName().endsWith(".jar"); + } + }); + + Assume.assumeTrue(jarfiles.length > 0); // trigger @Ignore if no jar files. + + URL urls[] = new URL[jarfiles.length]; + for (int i = 0; i < jarfiles.length; i++) + { + urls[i] = jarfiles[i].toURI().toURL(); + // System.out.println("Adding test-jar => " + urls[i]); + } + + return new URLClassLoader(urls,parentClassLoader); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java new file mode 100644 index 00000000000..b9b536db806 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java @@ -0,0 +1,46 @@ +package org.eclipse.jetty.util.log; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.Assert; + +public class StdErrCapture +{ + private ByteArrayOutputStream test; + private PrintStream err; + + public StdErrCapture(StdErrLog log) + { + this(); + log.setStdErrStream(err); + } + + public StdErrCapture() + { + test = new ByteArrayOutputStream(); + err = new PrintStream(test); + } + + public void capture(StdErrLog log) + { + log.setStdErrStream(err); + } + + public void assertContains(String expectedString) + { + err.flush(); + String output = new String(test.toByteArray()); + Assert.assertThat(output,containsString(expectedString)); + } + + public void assertNotContains(String unexpectedString) + { + err.flush(); + String output = new String(test.toByteArray()); + Assert.assertThat(output,not(containsString(unexpectedString))); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java index 017816bdf25..6a0e8e2b622 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java @@ -13,70 +13,635 @@ package org.eclipse.jetty.util.log; -import junit.framework.TestCase; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Properties; + +import org.junit.Assert; +import org.junit.Test; /** - * @author mgorovoy - * */ -public class StdErrLogTest extends TestCase + * Tests for StdErrLog + */ +public class StdErrLogTest { - public void testNullValues() + @Test + public void testStdErrLogFormat() throws UnsupportedEncodingException { - StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + StdErrLog log = new StdErrLog(LogTest.class.getName()); + StdErrCapture output = new StdErrCapture(log); + + log.info("testing:{},{}","test","format1"); + log.info("testing:{}","test","format2"); + log.info("testing","test","format3"); + log.info("testing:{},{}","test",null); + log.info("testing {} {}",null,null); + log.info("testing:{}",null,null); + log.info("testing",null,null); + + output.assertContains("INFO:oejul.LogTest:testing:test,format1"); + output.assertContains("INFO:oejul.LogTest:testing:test,format1"); + output.assertContains("INFO:oejul.LogTest:testing:test format2"); + output.assertContains("INFO:oejul.LogTest:testing test format3"); + output.assertContains("INFO:oejul.LogTest:testing:test,null"); + output.assertContains("INFO:oejul.LogTest:testing null null"); + output.assertContains("INFO:oejul.LogTest:testing:null"); + output.assertContains("INFO:oejul.LogTest:testing"); + } + + @Test + public void testStdErrLogDebug() + { + StdErrLog log = new StdErrLog("xxx"); + StdErrCapture output = new StdErrCapture(log); + + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.debug("testing {} {}","test","debug"); + log.info("testing {} {}","test","info"); + log.warn("testing {} {}","test","warn"); + log.setLevel(StdErrLog.LEVEL_INFO); + log.debug("YOU SHOULD NOT SEE THIS!",null,null); + + // Test for backward compat with old (now deprecated) method log.setDebugEnabled(true); - log.setHideStacks(true); + log.debug("testing {} {}","test","debug-deprecated"); - try { - log.info("Testing info(msg,null,null) - {} {}","arg0","arg1"); - log.info("Testing info(msg,null,null) - {} {}",null,null); - log.info("Testing info(msg,null,null) - {}",null,null); - log.info("Testing info(msg,null,null)",null,null); - log.info(null,"Testing","info(null,arg0,arg1)"); - log.info(null,null,null); + log.setDebugEnabled(false); + log.debug("testing {} {}","test","debug-deprecated-false"); - log.debug("Testing debug(msg,null,null) - {} {}","arg0","arg1"); - log.debug("Testing debug(msg,null,null) - {} {}",null,null); - log.debug("Testing debug(msg,null,null) - {}",null,null); - log.debug("Testing debug(msg,null,null)",null,null); - log.debug(null,"Testing","debug(null,arg0,arg1)"); - log.debug(null,null,null); - - log.debug("Testing debug(msg,null)"); - log.debug(null,new Throwable("Testing debug(null,thrw)").fillInStackTrace()); - - log.warn("Testing warn(msg,null,null) - {} {}","arg0","arg1"); - log.warn("Testing warn(msg,null,null) - {} {}",null,null); - log.warn("Testing warn(msg,null,null) - {}",null,null); - log.warn("Testing warn(msg,null,null)",null,null); - log.warn(null,"Testing","warn(msg,arg0,arg1)"); - log.warn(null,null,null); - - log.warn("Testing warn(msg,null)"); - log.warn(null,new Throwable("Testing warn(msg,thrw)").fillInStackTrace()); - } - catch (NullPointerException npe) - { - System.err.println(npe); - npe.printStackTrace(); - assertTrue("NullPointerException in StdErrLog.", false); - } + output.assertContains("DBUG:xxx:testing test debug"); + output.assertContains("INFO:xxx:testing test info"); + output.assertContains("WARN:xxx:testing test warn"); + output.assertNotContains("YOU SHOULD NOT SEE THIS!"); + output.assertContains("DBUG:xxx:testing test debug-deprecated"); + output.assertNotContains("DBUG:xxx:testing test debug-depdeprecated-false"); } - public void testIgnores() + @Test + public void testStdErrLogName() + { + StdErrLog log = new StdErrLog("test"); + log.setPrintLongNames(true); + StdErrCapture output = new StdErrCapture(log); + + Assert.assertThat("Log.name", log.getName(), is("test")); + Logger next=log.getLogger("next"); + Assert.assertThat("Log.name(child)", next.getName(), is("test.next")); + next.info("testing {} {}","next","info"); + + output.assertContains(":test.next:testing next info"); + } + + @Test + public void testStdErrThrowable() + { + // Common Throwable (for test) + Throwable th = new Throwable("Message"); + + // Capture raw string form + StringWriter tout = new StringWriter(); + th.printStackTrace(new PrintWriter(tout)); + String ths = tout.toString(); + + // Start test + StdErrLog log = new StdErrLog("test"); + StdErrCapture output = new StdErrCapture(log); + + log.warn("ex",th); + output.assertContains(ths); + + th = new Throwable("Message with \033 escape"); + + log.warn("ex",th); + output.assertNotContains("Message with \033 escape"); + log.info(th.toString()); + output.assertNotContains("Message with \033 escape"); + + log.warn("ex",th); + output.assertContains("Message with ? escape"); + log.info(th.toString()); + output.assertContains("Message with ? escape"); + } + + /** + * Test to make sure that using a Null parameter on parameterized messages does not result in a NPE + */ + @Test + public void testParameterizedMessage_NullValues() throws NullPointerException + { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.setHideStacks(true); + + log.info("Testing info(msg,null,null) - {} {}","arg0","arg1"); + log.info("Testing info(msg,null,null) - {} {}",null,null); + log.info("Testing info(msg,null,null) - {}",null,null); + log.info("Testing info(msg,null,null)",null,null); + log.info(null,"Testing","info(null,arg0,arg1)"); + log.info(null,null,null); + + log.debug("Testing debug(msg,null,null) - {} {}","arg0","arg1"); + log.debug("Testing debug(msg,null,null) - {} {}",null,null); + log.debug("Testing debug(msg,null,null) - {}",null,null); + log.debug("Testing debug(msg,null,null)",null,null); + log.debug(null,"Testing","debug(null,arg0,arg1)"); + log.debug(null,null,null); + + log.debug("Testing debug(msg,null)"); + log.debug(null,new Throwable("Testing debug(null,thrw)").fillInStackTrace()); + + log.warn("Testing warn(msg,null,null) - {} {}","arg0","arg1"); + log.warn("Testing warn(msg,null,null) - {} {}",null,null); + log.warn("Testing warn(msg,null,null) - {}",null,null); + log.warn("Testing warn(msg,null,null)",null,null); + log.warn(null,"Testing","warn(msg,arg0,arg1)"); + log.warn(null,null,null); + + log.warn("Testing warn(msg,null)"); + log.warn(null,new Throwable("Testing warn(msg,thrw)").fillInStackTrace()); + } + + @Test + public void testGetLoggingLevel_Default() + { + Properties props = new Properties(); + + // Default Levels + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,null)); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"")); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName())); + } + + @Test + public void testGetLoggingLevel_Bad() + { + Properties props = new Properties(); + props.setProperty("log.LEVEL", "WARN"); + props.setProperty("org.eclipse.jetty.bad.LEVEL","FRUIT"); + + // Default Level (because of bad level value) + Assert.assertEquals("Bad Logging Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.bad")); + } + + @Test + public void testGetLoggingLevel_Lowercase() + { + Properties props = new Properties(); + props.setProperty("log.LEVEL", "warn"); + props.setProperty("org.eclipse.jetty.util.LEVEL","info"); + + // Default Level + Assert.assertEquals("Lowercase Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + // Specific Level + Assert.assertEquals("Lowercase Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util")); + } + + @Test + public void testGetLoggingLevel_Root() + { + Properties props = new Properties(); + props.setProperty("log.LEVEL","DEBUG"); + + // Default Levels + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,null)); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"")); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName())); + } + + @Test + public void testGetLoggingLevel_FQCN() + { + String name = StdErrLogTest.class.getName(); + Properties props = new Properties(); + props.setProperty(name + ".LEVEL","ALL"); + + // Default Levels + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,null)); + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"")); + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + + // Specified Level + Assert.assertEquals(StdErrLog.LEVEL_ALL,StdErrLog.getLoggingLevel(props,name)); + } + + @Test + public void testGetLoggingLevel_UtilLevel() + { + Properties props = new Properties(); + props.setProperty("org.eclipse.jetty.util.LEVEL","DEBUG"); + + // Default Levels + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,null)); + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"")); + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.server.BogusObject")); + + // Configured Level + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName())); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.Bogus")); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util")); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.resource.FileResource")); + } + + @Test + public void testGetLoggingLevel_MixedLevels() + { + Properties props = new Properties(); + props.setProperty("log.LEVEL","DEBUG"); + props.setProperty("org.eclipse.jetty.util.LEVEL","WARN"); + props.setProperty("org.eclipse.jetty.util.ConcurrentHashMap.LEVEL","ALL"); + + // Default Levels + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,null)); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"")); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty")); + Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.server.ServerObject")); + + // Configured Level + Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName())); + Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.MagicUtil")); + Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util")); + Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.resource.FileResource")); + Assert.assertEquals(StdErrLog.LEVEL_ALL,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.ConcurrentHashMap")); + } + + /** + * Tests StdErrLog.warn() methods with level filtering. + *

    + * Should always see WARN level messages, regardless of set level. + */ + @Test + public void testWarnFiltering() throws UnsupportedEncodingException + { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setHideStacks(false); + + StdErrCapture output = new StdErrCapture(log); + + // Start with default level + log.warn("See Me"); + + // Set to debug level + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.warn("Hear Me"); + + // Set to warn level + log.setLevel(StdErrLog.LEVEL_WARN); + log.warn("Cheer Me"); + + log.warn("", new Throwable("out of focus")); + log.warn(new Throwable("scene lost")); + + // Validate Output + // System.err.print(output); + output.assertContains("See Me"); + output.assertContains("Hear Me"); + output.assertContains("Cheer Me"); + + // Validate Stack Traces + output.assertContains(".StdErrLogTest:"); + output.assertContains("java.lang.Throwable: out of focus"); + output.assertContains("java.lang.Throwable: scene lost"); + } + + /** + * Tests StdErrLog.info() methods with level filtering. + *

    + * Should only see INFO level messages when level is set to {@link StdErrLog#LEVEL_INFO} and below. + */ + @Test + public void testInfoFiltering() throws UnsupportedEncodingException + { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setHideStacks(false); + + StdErrCapture output = new StdErrCapture(log); + + // Normal/Default behavior + log.info("I will not buy"); + + // Level Debug + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.info("this record"); + + // Level All + log.setLevel(StdErrLog.LEVEL_ALL); + log.info("it is scratched."); + + log.info("", new Throwable("out of focus")); + log.info(new Throwable("scene lost")); + + // Level Warn + log.setLevel(StdErrLog.LEVEL_WARN); + log.info("sorry?"); + log.info("", new Throwable("on editing room floor")); + + // Validate Output + output.assertContains("I will not buy"); + output.assertContains("this record"); + output.assertContains("it is scratched."); + output.assertNotContains("sorry?"); + + // Validate Stack Traces + output.assertNotContains(""); + output.assertNotContains("on editing room floor"); + + output.assertContains(".StdErrLogTest:"); + output.assertContains("java.lang.Throwable: out of focus"); + output.assertContains("java.lang.Throwable: scene lost"); + } + + /** + * Tests StdErrLog.debug() methods with level filtering. + *

    + * Should only see DEBUG level messages when level is set to {@link StdErrLog#LEVEL_DEBUG} and below. + */ + @Test + public void testDebugFiltering() throws UnsupportedEncodingException { StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); log.setHideStacks(true); - - Log.__ignored=false; - log.setDebugEnabled(false); + + StdErrCapture output = new StdErrCapture(log); + + // Normal/Default behavior + log.debug("Tobacconist"); + log.debug("", new Throwable("on editing room floor")); + + // Level Debug + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.debug("my hovercraft is"); + + log.debug("", new Throwable("out of focus")); + log.debug(new Throwable("scene lost")); + + // Level All + log.setLevel(StdErrLog.LEVEL_ALL); + log.debug("full of eels."); + + // Level Warn + log.setLevel(StdErrLog.LEVEL_WARN); + log.debug("what?"); + + // Validate Output + // System.err.print(output); + output.assertNotContains("Tobacconist"); + output.assertContains("my hovercraft is"); + output.assertContains("full of eels."); + output.assertNotContains("what?"); + + // Validate Stack Traces + output.assertNotContains(""); + output.assertNotContains("on editing room floor"); + + output.assertContains(".StdErrLogTest:"); + output.assertContains("java.lang.Throwable: out of focus"); + output.assertContains("java.lang.Throwable: scene lost"); + } + + /** + * Tests StdErrLog with {@link Logger#ignore(Throwable)} use. + *

    + * Should only see IGNORED level messages when level is set to {@link StdErrLog#LEVEL_ALL}. + */ + @Test + public void testIgnores() throws UnsupportedEncodingException + { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setHideStacks(true); + + StdErrCapture output = new StdErrCapture(log); + + // Normal/Default behavior log.ignore(new Throwable("IGNORE ME")); - Log.__ignored=true; - log.setDebugEnabled(false); + // Show Ignored + log.setLevel(StdErrLog.LEVEL_ALL); log.ignore(new Throwable("Don't ignore me")); - Log.__ignored=false; - log.setDebugEnabled(true); + // Set to Debug level + log.setLevel(StdErrLog.LEVEL_DEBUG); log.ignore(new Throwable("Debug me")); + + // Validate Output + // System.err.print(output); + output.assertNotContains("IGNORE ME"); + output.assertContains("Don't ignore me"); + output.assertNotContains("Debug me"); + } + + @Test + public void testIsDebugEnabled() { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setHideStacks(true); + + log.setLevel(StdErrLog.LEVEL_ALL); + Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true)); + + log.setLevel(StdErrLog.LEVEL_DEBUG); + Assert.assertThat("log.level(debug).isDebugEnabled", log.isDebugEnabled(), is(true)); + + log.setLevel(StdErrLog.LEVEL_INFO); + Assert.assertThat("log.level(info).isDebugEnabled", log.isDebugEnabled(), is(false)); + + log.setLevel(StdErrLog.LEVEL_WARN); + Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false)); + } + + @Test + public void testSetGetLevel() + { + StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); + log.setHideStacks(true); + + log.setLevel(StdErrLog.LEVEL_ALL); + Assert.assertThat("log.level(all).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_ALL)); + + log.setLevel(StdErrLog.LEVEL_DEBUG); + Assert.assertThat("log.level(debug).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_DEBUG)); + + log.setLevel(StdErrLog.LEVEL_INFO); + Assert.assertThat("log.level(info).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_INFO)); + + log.setLevel(StdErrLog.LEVEL_WARN); + Assert.assertThat("log.level(warn).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_WARN)); + } + + @Test + public void testGetChildLogger_Simple() + { + String baseName = "jetty"; + StdErrLog log = new StdErrLog(baseName); + log.setHideStacks(true); + + Assert.assertThat("Logger.name", log.getName(), is("jetty")); + + Logger log2 = log.getLogger("child"); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child")); + } + + @Test + public void testGetChildLogger_Deep() + { + String baseName = "jetty"; + StdErrLog log = new StdErrLog(baseName); + log.setHideStacks(true); + + Assert.assertThat("Logger.name", log.getName(), is("jetty")); + + Logger log2 = log.getLogger("child.of.the.sixties"); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child.of.the.sixties")); + } + + @Test + public void testGetChildLogger_Null() + { + String baseName = "jetty"; + StdErrLog log = new StdErrLog(baseName); + log.setHideStacks(true); + + Assert.assertThat("Logger.name", log.getName(), is("jetty")); + + // Pass null as child reference, should return parent logger + Logger log2 = log.getLogger(null); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty")); + Assert.assertSame("Should have returned same logger", log2, log); + } + + @Test + public void testGetChildLogger_EmptyName() + { + String baseName = "jetty"; + StdErrLog log = new StdErrLog(baseName); + log.setHideStacks(true); + + Assert.assertThat("Logger.name", log.getName(), is("jetty")); + + // Pass empty name as child reference, should return parent logger + Logger log2 = log.getLogger(""); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty")); + Assert.assertSame("Should have returned same logger", log2, log); + } + + @Test + public void testGetChildLogger_EmptyNameSpaces() + { + String baseName = "jetty"; + StdErrLog log = new StdErrLog(baseName); + log.setHideStacks(true); + + Assert.assertThat("Logger.name", log.getName(), is("jetty")); + + // Pass empty name as child reference, should return parent logger + Logger log2 = log.getLogger(" "); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty")); + Assert.assertSame("Should have returned same logger", log2, log); + } + + @Test + public void testGetChildLogger_NullParent() + { + StdErrLog log = new StdErrLog(null); + + Assert.assertThat("Logger.name", log.getName(), is("")); + + Logger log2 = log.getLogger("jetty"); + Assert.assertThat("Logger.child.name", log2.getName(), is("jetty")); + Assert.assertNotSame("Should have returned same logger", log2, log); + } + + @Test + public void testToString() + { + StdErrLog log = new StdErrLog("jetty"); + + log.setLevel(StdErrLog.LEVEL_ALL); + Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=ALL")); + + log.setLevel(StdErrLog.LEVEL_DEBUG); + Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=DEBUG")); + + log.setLevel(StdErrLog.LEVEL_INFO); + Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=INFO")); + + log.setLevel(StdErrLog.LEVEL_WARN); + Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=WARN")); + + log.setLevel(99); // intentionally bogus level + Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=?")); + } + + @Test + public void testPrintSource() throws UnsupportedEncodingException + { + StdErrLog log = new StdErrLog("test"); + log.setLevel(StdErrLog.LEVEL_DEBUG); + log.setSource(true); + + ByteArrayOutputStream test = new ByteArrayOutputStream(); + PrintStream err = new PrintStream(test); + log.setStdErrStream(err); + + log.debug("Show me the source!"); + + String output = new String(test.toByteArray(),"UTF-8"); + // System.err.print(output); + + Assert.assertThat(output, containsString(".StdErrLogTest#testPrintSource(StdErrLogTest.java:")); + } + + @Test + public void testConfiguredAndSetDebugEnabled() + { + Properties props = new Properties(); + props.setProperty("org.eclipse.jetty.util.LEVEL","WARN"); + props.setProperty("org.eclipse.jetty.io.LEVEL", "WARN"); + + StdErrLog root = new StdErrLog("", props); + assertLevel(root,StdErrLog.LEVEL_INFO); // default + + StdErrLog log = (StdErrLog)root.getLogger(StdErrLogTest.class.getName()); + Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false)); + assertLevel(log,StdErrLog.LEVEL_WARN); // as configured + + // Boot stomp it all to debug + root.setDebugEnabled(true); + Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(true)); + assertLevel(log,StdErrLog.LEVEL_DEBUG); // as stomped + + // Restore configured + root.setDebugEnabled(false); + Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false)); + assertLevel(log,StdErrLog.LEVEL_WARN); // as configured + } + + private void assertLevel(StdErrLog log, int expectedLevel) + { + Assert.assertThat("Log[" + log.getName() + "].level",levelToString(log.getLevel()),is(levelToString(expectedLevel))); + } + + private String levelToString(int level) + { + switch (level) + { + case StdErrLog.LEVEL_ALL: + return "ALL"; + case StdErrLog.LEVEL_DEBUG: + return "DEBUG"; + case StdErrLog.LEVEL_INFO: + return "INFO"; + case StdErrLog.LEVEL_WARN: + return "WARN"; + default: + return Integer.toString(level); + } } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java index c8d46eff9e8..aa5c14815a2 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -464,9 +464,22 @@ public class ResourceTest // This test is intended to run only on Windows platform assumeTrue(OS.IS_WINDOWS); + String path = __userURL.toURI().getPath().replace('/','\\')+"ResourceTest.java"; + System.err.println(path); + + Resource resource = Resource.newResource(path, false); + System.err.println(resource); + assertTrue(resource.exists()); + + /* + String uncPath = "\\\\127.0.0.1"+__userURL.toURI().getPath().replace('/','\\').replace(':','$')+"ResourceTest.java"; + System.err.println(uncPath); Resource uncResource = Resource.newResource(uncPath, false); - assertTrue(uncResource.exists()); + System.err.println(uncResource); + assertTrue(uncResource.exists()); + + */ } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java new file mode 100644 index 00000000000..4e2c7e1861d --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -0,0 +1,183 @@ +package org.eclipse.jetty.util.ssl; + +import static junit.framework.Assert.assertTrue; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.Assert; +import org.junit.Test; + + +public class SslContextFactoryTest +{ + @Test + public void testNoTsFileKs() throws Exception + { + String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; + SslContextFactory cf = new SslContextFactory(keystorePath); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + + cf.start(); + + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testNoTsStreamKs() throws Exception + { + String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; + + SslContextFactory cf = new SslContextFactory(); + + cf.setKeyStoreInputStream(new FileInputStream(keystorePath)); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + + cf.start(); + + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testNoTsSetKs() throws Exception + { + String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; + + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(new FileInputStream(keystorePath),"storepwd".toCharArray()); + + SslContextFactory cf = new SslContextFactory(); + cf.setKeyStore(ks); + cf.setKeyManagerPassword("keypwd"); + + cf.start(); + + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testNoTsNoKs() throws Exception + { + SslContextFactory cf = new SslContextFactory(); + cf.start(); + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testTrustAll() throws Exception + { + SslContextFactory cf = new SslContextFactory(); + cf.start(); + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testNoTsResourceKs() throws Exception + { + Resource keystoreResource = Resource.newSystemResource("keystore"); + + SslContextFactory cf = new SslContextFactory(); + cf.setKeyStoreResource(keystoreResource); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + + cf.start(); + + assertTrue(cf.getSslContext()!=null); + + } + + @Test + public void testResourceTsResourceKs() throws Exception + { + Resource keystoreResource = Resource.newSystemResource("keystore"); + Resource truststoreResource = Resource.newSystemResource("keystore"); + + SslContextFactory cf = new SslContextFactory(); + cf.setKeyStoreResource(keystoreResource); + cf.setTrustStoreResource(truststoreResource); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + cf.setTrustStorePassword("storepwd"); + + cf.start(); + + assertTrue(cf.getSslContext()!=null); + } + + @Test + public void testResourceTsResourceKsWrongPW() throws Exception + { + Resource keystoreResource = Resource.newSystemResource("keystore"); + Resource truststoreResource = Resource.newSystemResource("keystore"); + + SslContextFactory cf = new SslContextFactory(); + cf.setKeyStoreResource(keystoreResource); + cf.setTrustStoreResource(truststoreResource); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("wrong_keypwd"); + cf.setTrustStorePassword("storepwd"); + + try + { + ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true); + cf.start(); + Assert.fail(); + } + catch(java.security.UnrecoverableKeyException e) + { + } + } + + @Test + public void testResourceTsWrongPWResourceKs() throws Exception + { + Resource keystoreResource = Resource.newSystemResource("keystore"); + Resource truststoreResource = Resource.newSystemResource("keystore"); + + SslContextFactory cf = new SslContextFactory(); + cf.setKeyStoreResource(keystoreResource); + cf.setTrustStoreResource(truststoreResource); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + cf.setTrustStorePassword("wrong_storepwd"); + + try + { + ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true); + cf.start(); + Assert.fail(); + } + catch(IOException e) + { + } + } + + @Test + public void testNoKeyConfig() throws Exception + { + SslContextFactory cf = new SslContextFactory(); + try + { + ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true); + cf.setTrustStore("/foo"); + cf.start(); + Assert.fail(); + } + catch (IllegalStateException e) + { + + } + catch (Exception e) + { + Assert.fail("Unexpected exception"); + } + } +} diff --git a/jetty-util/src/test/resources/jetty-logging.properties b/jetty-util/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..1fdedf40df2 --- /dev/null +++ b/jetty-util/src/test/resources/jetty-logging.properties @@ -0,0 +1,2 @@ +# Setup default logging implementation for during testing +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog \ No newline at end of file diff --git a/jetty-util/src/test/resources/keystore b/jetty-util/src/test/resources/keystore new file mode 100644 index 00000000000..b727bd0fb77 Binary files /dev/null and b/jetty-util/src/test/resources/keystore differ diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 649c5a3fe5b..eb1971c5043 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-webapp diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java index d6cc0be137c..57df7945011 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java @@ -54,12 +54,11 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration //cannot configure if the _context is already started if (context.isStarted()) { - if (LOG.isDebugEnabled()){LOG.debug("Cannot configure webapp after it is started");} + LOG.debug("Cannot configure webapp after it is started"); return; } - if(LOG.isDebugEnabled()) - LOG.debug("Configuring web-jetty.xml"); + LOG.debug("Configuring web-jetty.xml"); Resource web_inf = context.getWebInf(); // handle any WEB-INF descriptors @@ -79,9 +78,8 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration try { context.setServerClasses(null); - if(LOG.isDebugEnabled()) { + if(LOG.isDebugEnabled()) LOG.debug("Configure: "+jetty); - } XmlConfiguration jetty_config = (XmlConfiguration)context.getAttribute(XML_CONFIGURATION); diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 85ab42083c1..db5ec955afa 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -26,7 +26,6 @@ import java.util.Map; import javax.servlet.ServletException; -import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.authentication.FormAuthenticator; @@ -42,6 +41,7 @@ import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.xml.XmlParser; /** @@ -147,7 +147,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor break; } } - if (LOG.isDebugEnabled()) LOG.debug("ContextParam: " + name + "=" + value); + if (LOG.isDebugEnabled()) + LOG.debug("ContextParam: " + name + "=" + value); } @@ -271,11 +272,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } } - // TODO is this too soon? /* Set the webapp's classpath for Jasper */ context.setAttribute("org.apache.catalina.jsp_classpath", context.getClassPath()); + /* Set the system classpath for Jasper */ - holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); + holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); } //Set the servlet-class @@ -321,6 +322,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { holder.setForcedPath(jsp_file); holder.setClassName(jspServletClass); + //set the system classpath explicitly for the holder that will represent the JspServlet instance + holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); } // handle load-on-startup diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java index f5f14b8e4d7..5adda2233a8 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java @@ -14,13 +14,16 @@ package org.eclipse.jetty.webapp; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.EventListener; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.servlet.Servlet; @@ -58,6 +61,7 @@ public class TagLibConfiguration extends AbstractConfiguration public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds"; + /** * TagLibListener * @@ -96,7 +100,37 @@ public class TagLibConfiguration extends AbstractConfiguration public void contextInitialized(ServletContextEvent sce) { - try { + try + { + //For jasper 2.1: + //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath + try + { + Class clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache"); + Collection tld_resources = (Collection)_context.getAttribute(TLD_RESOURCES); + + Map> tldMap = new HashMap>(); + + if (tld_resources != null) + { + //get the jar file names of the files + for (Resource r:tld_resources) + { + Resource jarResource = extractJarResource(r); + //jasper is happy with an empty list of tlds + if (!tldMap.containsKey(jarResource.getURI())) + tldMap.put(jarResource.getURI(), null); + + } + //set the magic context attribute that tells jasper about the system tlds + sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap); + } + } + catch (ClassNotFoundException e) + { + LOG.ignore(e); + } + //find the tld files and parse them to get out their //listeners Set tlds = findTldResources(); @@ -117,12 +151,37 @@ public class TagLibConfiguration extends AbstractConfiguration } } - } catch (Exception e) { + } + catch (Exception e) { LOG.warn(e); } } + + + private Resource extractJarResource (Resource r) + { + if (r == null) + return null; + + try + { + String url = r.getURI().toURL().toString(); + int idx = url.lastIndexOf("!/"); + if (idx >= 0) + url = url.substring(0, idx); + if (url.startsWith("jar:")) + url = url.substring(4); + return Resource.newResource(url); + } + catch (IOException e) + { + LOG.warn(e); + return null; + } + } + /** * Find all the locations that can harbour tld files that may contain * a listener which the web container is supposed to instantiate and @@ -378,7 +437,8 @@ public class TagLibConfiguration extends AbstractConfiguration public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node) { String className=node.getString("listener-class",false,true); - if (LOG.isDebugEnabled()) LOG.debug("listener="+className); + if (LOG.isDebugEnabled()) + LOG.debug("listener="+className); try { diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index 6b968e18df1..60ff124635f 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -60,9 +60,9 @@ import org.eclipse.jetty.util.resource.ResourceCollection; * The handlers are configured by pluggable configuration classes, with * the default being {@link org.eclipse.jetty.webapp.WebXmlConfiguration} and * {@link org.eclipse.jetty.webapp.JettyWebXmlConfiguration}. - * + * * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base" - * + * */ public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context { @@ -75,7 +75,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration"; public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses"; public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses"; - + private static String[] __dftConfigurationClasses = { "org.eclipse.jetty.webapp.WebInfConfiguration", @@ -85,38 +85,40 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL "org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.webapp.TagLibConfiguration" } ; - - // System classes are classes that cannot be replaced by - // the web application, and they are *always* loaded via + + // System classes are classes that cannot be replaced by + // the web application, and they are *always* loaded via // system classloader. - public final static String[] __dftSystemClasses = + public final static String[] __dftSystemClasses = { - "java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) "javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) "org.xml.", // needed by javax.xml "org.w3c.", // needed by javax.xml - "org.apache.commons.logging.", // TODO: review if special case still needed + "org.apache.commons.logging.", // TODO: review if special case still needed "org.eclipse.jetty.continuation.", // webapp cannot change continuation classes "org.eclipse.jetty.jndi.", // webapp cannot change naming classes "org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes - "org.eclipse.jetty.websocket.", // WebSocket is a jetty extension - "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets + "org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension + "org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension + "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets } ; - + // Server classes are classes that are hidden from being // loaded by the web application using system classloader, // so if web application needs to load any of such classes, // it has to include them in its distribution. - public final static String[] __dftServerClasses = + public final static String[] __dftServerClasses = { "-org.eclipse.jetty.continuation.", // don't hide continuation classes "-org.eclipse.jetty.jndi.", // don't hide naming classes "-org.eclipse.jetty.plus.jaas.", // don't hide jaas classes - "-org.eclipse.jetty.websocket.", // don't hide websocket extension + "-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension + "-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet - "-org.eclipse.jetty.servlet.listener.", //don't hide useful listeners + "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners "org.eclipse.jetty." // hide other jetty classes - } ; + } ; private String[] _configurationClasses = __dftConfigurationClasses; private ClasspathPattern _systemClasses = null; @@ -135,12 +137,12 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL private PermissionCollection _permissions; private String[] _contextWhiteList = null; - + private File _tmpDir; private String _war; private String _extraClasspath; private Throwable _unavailableException; - + private Map _resourceAliases; private boolean _ownClassLoader=false; private boolean _configurationDiscovered=true; @@ -148,8 +150,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL private boolean _configurationsSet=false; private boolean _allowDuplicateFragmentNames = false; private boolean _throwUnavailableOnStartupException = false; - - + + private MetaData _metadata=new MetaData(); public static WebAppContext getCurrentWebAppContext() @@ -163,15 +165,15 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL } return null; } - + /* ------------------------------------------------------------ */ public WebAppContext() { - super(SESSIONS|SECURITY); + super(SESSIONS|SECURITY); _scontext=new Context(); - setErrorHandler(new ErrorPageErrorHandler()); + setErrorHandler(new ErrorPageErrorHandler()); } - + /* ------------------------------------------------------------ */ /** * @param contextPath The context path @@ -183,9 +185,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _scontext=new Context(); setContextPath(contextPath); setWar(webApp); - setErrorHandler(new ErrorPageErrorHandler()); + setErrorHandler(new ErrorPageErrorHandler()); } - + /* ------------------------------------------------------------ */ /** * @param parent The parent HandlerContainer. @@ -197,7 +199,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL super(parent,contextPath,SESSIONS|SECURITY); _scontext=new Context(); setWar(webApp); - setErrorHandler(new ErrorPageErrorHandler()); + setErrorHandler(new ErrorPageErrorHandler()); } /* ------------------------------------------------------------ */ @@ -215,7 +217,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _scontext = new Context(); setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler()); } - + /* ------------------------------------------------------------ */ /** * @param servletContextName The servletContextName to set. @@ -228,7 +230,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL if (cl!=null && cl instanceof WebAppClassLoader && servletContextName!=null) ((WebAppClassLoader)cl).setName(servletContextName); } - + /* ------------------------------------------------------------ */ /** Get an exception that caused the webapp to be unavailable * @return A throwable if the webapp is unavailable or null @@ -238,7 +240,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL return _unavailableException; } - + /* ------------------------------------------------------------ */ /** Set Resource Alias. * Resource aliases map resource uri's within a context. @@ -261,13 +263,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL return null; return _resourceAliases; } - + /* ------------------------------------------------------------ */ public void setResourceAliases(Map map) { _resourceAliases = map; } - + /* ------------------------------------------------------------ */ public String getResourceAlias(String alias) { @@ -292,16 +294,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL public void setClassLoader(ClassLoader classLoader) { super.setClassLoader(classLoader); - + // if ( !(classLoader instanceof WebAppClassLoader) ) // { // LOG.info("NOTE: detected a classloader which is not an instance of WebAppClassLoader being set on WebAppContext, some typical class and resource locations may be missing on: " + toString() ); // } - + if (classLoader!=null && classLoader instanceof WebAppClassLoader && getDisplayName()!=null) ((WebAppClassLoader)classLoader).setName(getDisplayName()); } - + /* ------------------------------------------------------------ */ @Override public Resource getResource(String uriInContext) throws MalformedURLException @@ -319,7 +321,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL resource= super.getResource(uriInContext); if (resource != null && resource.exists()) return resource; - + uriInContext = getResourceAlias(uriInContext); } catch (IOException e) @@ -335,11 +337,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL return resource; } - + /* ------------------------------------------------------------ */ /** Is the context Automatically configured. - * + * * @return true if configuration discovery. */ public boolean isConfigurationDiscovered() @@ -361,7 +363,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { _configurationDiscovered = discovered; } - + /* ------------------------------------------------------------ */ /** Pre configure the web application. *

    @@ -372,7 +374,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL *

  • Setup the default System classes by calling {@link #loadSystemClasses()} *
  • Setup the default Server classes by calling loadServerClasses() *
  • Instantiates a classload (if one is not already set) - *
  • Calls the {@link Configuration#preConfigure(WebAppContext)} method of all + *
  • Calls the {@link Configuration#preConfigure(WebAppContext)} method of all * Configuration instances. * * @throws Exception @@ -384,7 +386,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL // Setup system classes loadSystemClasses(); - + // Setup server classes loadServerClasses(); @@ -400,16 +402,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL if (LOG.isDebugEnabled()) { ClassLoader loader = getClassLoader(); - LOG.debug("Thread Context class loader is: " + loader); + LOG.debug("Thread Context classloader {}",loader); loader=loader.getParent(); while(loader!=null) { - LOG.debug("Parent class loader is: " + loader); + LOG.debug("Parent class loader: {} ",loader); loader=loader.getParent(); } } - - // Prepare for configuration + + // Prepare for configuration for (int i=0;i<_configurations.length;i++) { LOG.debug("preConfigure {} with {}",this,_configurations[i]); @@ -427,10 +429,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _configurations[i].configure(this); } } - + /* ------------------------------------------------------------ */ public void postConfigure() throws Exception - { + { // Clean up after configuration for (int i=0;i<_configurations.length;i++) { @@ -438,7 +440,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _configurations[i].postConfigure(this); } } - + /* ------------------------------------------------------------ */ /* * @see org.eclipse.thread.AbstractLifeCycle#doStart() @@ -466,7 +468,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL throw e; } } - + /* ------------------------------------------------------------ */ /* * @see org.eclipse.thread.AbstractLifeCycle#doStop() @@ -480,11 +482,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { for (int i=_configurations.length;i-->0;) _configurations[i].deconfigure(this); - + if (_metadata != null) _metadata.clear(); _metadata=new MetaData(); - + } finally { @@ -495,19 +497,19 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _unavailableException=null; } } - + /* ------------------------------------------------------------ */ @Override public void destroy() { - // Prepare for configuration + // Prepare for configuration MultiException mx=new MultiException(); if (_configurations!=null) { for (int i=_configurations.length;i-->0;) { try - { + { _configurations[i].destroy(this); } catch(Exception e) @@ -535,7 +537,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL String displayName = getDisplayName(); if (displayName == null) displayName = "WebApp@"+connectors.hashCode(); - + LOG.info(displayName + " at http://" + connectorName + getContextPath()); } } @@ -548,7 +550,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { return _configurationClasses; } - + /* ------------------------------------------------------------ */ /** * @return Returns the configurations. @@ -557,7 +559,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { return _configurations; } - + /* ------------------------------------------------------------ */ /** * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml @@ -567,7 +569,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { return _defaultsDescriptor; } - + /* ------------------------------------------------------------ */ /** * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml @@ -580,7 +582,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL return null; return _overrideDescriptors.get(0); } - + /* ------------------------------------------------------------ */ /** * An override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml @@ -590,7 +592,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { return Collections.unmodifiableList(_overrideDescriptors); } - + /* ------------------------------------------------------------ */ /** * @return Returns the permissions. @@ -609,7 +611,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { if (_serverClasses == null) loadServerClasses(); - + return _serverClasses.getPatterns(); } @@ -617,10 +619,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { if (_serverClasses == null) loadServerClasses(); - + _serverClasses.addPattern(classname); } - + /* ------------------------------------------------------------ */ /** * @see #setSystemClasses(String[]) @@ -630,25 +632,25 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { if (_systemClasses == null) loadSystemClasses(); - + return _systemClasses.getPatterns(); } - + /* ------------------------------------------------------------ */ public void addSystemClass(String classname) { if (_systemClasses == null) loadSystemClasses(); - + _systemClasses.addPattern(classname); } - + /* ------------------------------------------------------------ */ public boolean isServerClass(String name) { if (_serverClasses == null) loadServerClasses(); - + return _serverClasses.match(name); } @@ -657,16 +659,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { if (_systemClasses == null) loadSystemClasses(); - + return _systemClasses.match(name); } - + /* ------------------------------------------------------------ */ protected void loadSystemClasses() { if (_systemClasses != null) return; - + //look for a Server attribute with the list of System classes //to apply to every web application. If not present, use our defaults. Server server = getServer(); @@ -676,17 +678,17 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL if (systemClasses != null && systemClasses instanceof String[]) _systemClasses = new ClasspathPattern((String[])systemClasses); } - + if (_systemClasses == null) _systemClasses = new ClasspathPattern(__dftSystemClasses); } - + /* ------------------------------------------------------------ */ private void loadServerClasses() { if (_serverClasses != null) return; - + //look for a Server attribute with the list of Server classes //to apply to every web application. If not present, use our defaults. Server server = getServer(); @@ -696,11 +698,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL if (serverClasses != null || serverClasses instanceof String[]) _serverClasses = new ClasspathPattern((String[])serverClasses); } - + if (_serverClasses == null) _serverClasses = new ClasspathPattern(__dftServerClasses); } - + /* ------------------------------------------------------------ */ /** * @return Returns the war as a file or URL string (Resource) @@ -722,10 +724,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL Resource web_inf= super.getBaseResource().addPath("WEB-INF/"); if (!web_inf.exists() || !web_inf.isDirectory()) return null; - + return web_inf; } - + /* ------------------------------------------------------------ */ /** * @return Returns the distributable. @@ -764,35 +766,35 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL /* ------------------------------------------------------------ */ /** - * @return True if the classloader should delegate first to the parent - * classloader (standard java behaviour) or false if the classloader - * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet + * @return True if the classloader should delegate first to the parent + * classloader (standard java behaviour) or false if the classloader + * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet * spec recommendation). */ public boolean isParentLoaderPriority() { return _parentLoaderPriority; } - - + + /* ------------------------------------------------------------ */ public String[] getDefaultConfigurationClasses () { return __dftConfigurationClasses; } - + /* ------------------------------------------------------------ */ public String[] getDefaultServerClasses () { return __dftServerClasses; } - + /* ------------------------------------------------------------ */ public String[] getDefaultSystemClasses () { return __dftSystemClasses; } - + /* ------------------------------------------------------------ */ protected void loadConfigurations() throws Exception @@ -811,17 +813,17 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance(); } } - + /* ------------------------------------------------------------ */ @Override protected boolean isProtectedTarget(String target) { while (target.startsWith("//")) target=URIUtil.compactPath(target); - + return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf"); } - + /* ------------------------------------------------------------ */ @Override @@ -843,7 +845,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _configurationClassesSet = true; _configurations=null; } - + /* ------------------------------------------------------------ */ /** * @param configurations The configurations to set. @@ -877,7 +879,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _overrideDescriptors.clear(); _overrideDescriptors.add(overrideDescriptor); } - + /* ------------------------------------------------------------ */ /** * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml @@ -888,7 +890,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _overrideDescriptors.clear(); _overrideDescriptors.addAll(overrideDescriptors); } - + /* ------------------------------------------------------------ */ /** * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml @@ -916,7 +918,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { _descriptor=descriptor; } - + /* ------------------------------------------------------------ */ /** * @param distributable The distributable to set. @@ -932,13 +934,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { if (_sessionHandler!=null) _sessionHandler.clearEventListeners(); - + super.setEventListeners(eventListeners); - + for (int i=0; eventListeners!=null && i - - jetty-project - org.eclipse.jetty - 7.5.0-SNAPSHOT - - 4.0.0 - org.eclipse.jetty - jetty-websocket - Jetty :: Websocket - - ${project.groupId}.websocket - - - - org.eclipse.jetty - jetty-server - ${project.version} - - - javax.servlet - servlet-api - jar - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - - - - org.apache.felix - maven-bundle-plugin - true - - - - manifest - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - artifact-jar - - jar - - - - test-jar - - test-jar - - - - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - - - - org.codehaus.mojo - findbugs-maven-plugin - - org.eclipse.jetty.websocket.* - - - - + + + + jetty-project + org.eclipse.jetty + 7.6.0-SNAPSHOT + + + 4.0.0 + jetty-websocket + Jetty :: Websocket + + + ${project.groupId}.websocket + + + + + javax.servlet + servlet-api + provided + + + org.eclipse.jetty + jetty-server + ${project.version} + provided + + + org.eclipse.jetty + jetty-util + ${project.version} + + + org.eclipse.jetty + jetty-io + ${project.version} + + + org.eclipse.jetty + jetty-http + ${project.version} + + + org.eclipse.jetty.toolchain + jetty-test-helper + test + + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + + manifest + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + artifact-jar + + jar + + + + test-jar + + test-jar + + + + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.codehaus.mojo + findbugs-maven-plugin + + org.eclipse.jetty.websocket.* + + + + + diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java index 57b711c609f..0e2aff4dcc6 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.IOException; diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java index cd32aafb7c1..91bbf275f40 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.IOException; @@ -11,6 +26,9 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +/** + * @TODO Implement proposed deflate frame draft + */ public class DeflateFrameExtension extends AbstractExtension { private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class); @@ -18,7 +36,7 @@ public class DeflateFrameExtension extends AbstractExtension private int _minLength=8; private Deflater _deflater; private Inflater _inflater; - + public DeflateFrameExtension() { super("x-deflate-frame"); @@ -32,10 +50,10 @@ public class DeflateFrameExtension extends AbstractExtension if(super.init(parameters)) { _minLength=getInitParameter("minLength",_minLength); - + _deflater=new Deflater(); _inflater=new Inflater(); - + return true; } return false; @@ -46,7 +64,7 @@ public class DeflateFrameExtension extends AbstractExtension */ @Override public void onFrame(byte flags, byte opcode, Buffer buffer) - { + { if (getConnection().isControl(opcode) || !isFlag(flags,1)) { super.onFrame(flags,opcode,buffer); @@ -55,7 +73,7 @@ public class DeflateFrameExtension extends AbstractExtension if (buffer.array()==null) buffer=buffer.asMutableBuffer(); - + int length=0xff&buffer.get(); if (length>=0x7e) { @@ -63,10 +81,10 @@ public class DeflateFrameExtension extends AbstractExtension length=0; while(b-->0) length=0x100*length+(0xff&buffer.get()); - } - + } + // TODO check a max framesize - + _inflater.setInput(buffer.array(),buffer.getIndex(),buffer.length()); ByteArrayBuffer buf = new ByteArrayBuffer(length); try @@ -84,7 +102,7 @@ public class DeflateFrameExtension extends AbstractExtension catch(DataFormatException e) { LOG.warn(e); - getConnection().close(WebSocketConnectionD12.CLOSE_PROTOCOL,e.toString()); + getConnection().close(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,e.toString()); } } @@ -99,12 +117,12 @@ public class DeflateFrameExtension extends AbstractExtension super.addFrame(clearFlag(flags,1),opcode,content,offset,length); return; } - + // prepare the uncompressed input _deflater.reset(); _deflater.setInput(content,offset,length); _deflater.finish(); - + // prepare the output buffer byte[] out= new byte[length]; int out_offset=0; @@ -113,10 +131,10 @@ public class DeflateFrameExtension extends AbstractExtension if (length>0xffff) { out[out_offset++]=0x7f; - out[out_offset++]=(byte)((length>>56)&0x7f); - out[out_offset++]=(byte)((length>>48)&0xff); - out[out_offset++]=(byte)((length>>40)&0xff); - out[out_offset++]=(byte)((length>>32)&0xff); + out[out_offset++]=(byte)0; + out[out_offset++]=(byte)0; + out[out_offset++]=(byte)0; + out[out_offset++]=(byte)0; out[out_offset++]=(byte)((length>>24)&0xff); out[out_offset++]=(byte)((length>>16)&0xff); out[out_offset++]=(byte)((length>>8)&0xff); @@ -125,7 +143,7 @@ public class DeflateFrameExtension extends AbstractExtension else if (length >=0x7e) { out[out_offset++]=0x7e; - out[out_offset++]=(byte)(byte)(length>>8); + out[out_offset++]=(byte)(length>>8); out[out_offset++]=(byte)(length&0xff); } else @@ -134,7 +152,7 @@ public class DeflateFrameExtension extends AbstractExtension } int l = _deflater.deflate(out,out_offset,length-out_offset); - + if (_deflater.finished()) super.addFrame(setFlag(flags,1),opcode,out,0,l+out_offset); else diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java index b3c93ec4600..67a2a521f96 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.util.Map; diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java index 1c7c0c55459..24b1a5d21f0 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java @@ -1,24 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; public class FixedMaskGen implements MaskGen { - final byte[] _mask; + private final byte[] _mask; + public FixedMaskGen() { - _mask=new byte[]{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff}; + this(new byte[]{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff}); } - + public FixedMaskGen(byte[] mask) { - _mask=mask; + _mask=new byte[4]; + // Copy to avoid that external code keeps a reference + // to the array parameter to modify masking on-the-fly + System.arraycopy(mask, 0, _mask, 0, 4); } - + public void genMask(byte[] mask) { - mask[0]=_mask[0]; - mask[1]=_mask[1]; - mask[2]=_mask[2]; - mask[3]=_mask[3]; + System.arraycopy(_mask, 0, mask, 0, 4); } -} \ No newline at end of file +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java index 106f718be53..069aa17d7b0 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.IOException; diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java index c8036c69d92..2faac0525da 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; public class IdentityExtension extends AbstractExtension diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java index bbd339055b0..b5d42724f70 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; public interface MaskGen diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java index 889bb0d31e1..f5d2527cddb 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.util.Random; @@ -5,19 +20,23 @@ import java.util.Random; public class RandomMaskGen implements MaskGen { - final Random _random; + private final Random _random; + public RandomMaskGen() { - _random=new Random(); + this(new Random()); } - + public RandomMaskGen(Random random) { _random=random; } - + public void genMask(byte[] mask) { + // The assumption is that this code is always called + // with an external lock held to prevent concurrent access + // Otherwise we need to synchronize on the _random. _random.nextBytes(mask); } -} \ No newline at end of file +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java index 958d6a8eaed..12935192e0f 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ @@ -109,9 +124,28 @@ public interface WebSocket String getProtocol(); void sendMessage(String data) throws IOException; void sendMessage(byte[] data, int offset, int length) throws IOException; + + /** + * @deprecated Use {@link #close()} + */ void disconnect(); + + /** + * Close the connection with normal close code. + */ + void close(); + + /** Close the connection with specific closeCode and message. + * @param closeCode The close code to send, or -1 for no close code + * @param message The message to send or null for no message + */ + void close(int closeCode,String message); + boolean isOpen(); + /** + * @param ms The time in ms that the connection can be idle before closing + */ void setMaxIdleTime(int ms); /** @@ -124,6 +158,11 @@ public interface WebSocket */ void setMaxBinaryMessageSize(int size); + /** + * @return The time in ms that the connection can be idle before closing + */ + int getMaxIdleTime(); + /** * Size in characters of the maximum text message to be received * @return size <0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters @@ -136,34 +175,111 @@ public interface WebSocket */ int getMaxBinaryMessageSize(); } - + /** * Frame Level Connection *

    The Connection interface at the level of sending/receiving frames rather than messages. + * Also contains methods to decode/generate flags and opcodes without using constants, so that + * code can be written to work with multiple drafts of the protocol. * */ public interface FrameConnection extends Connection { - boolean isMessageComplete(byte flags); - void close(int closeCode,String message); + /** + * @return The opcode of a binary message + */ byte binaryOpcode(); - byte textOpcode(); - byte continuationOpcode(); - byte finMask(); - String getProtocol(); - void setFakeFragments(boolean fake); - boolean isFakeFragments(); + /** + * @return The opcode of a text message + */ + byte textOpcode(); + + /** + * @return The opcode of a continuation frame + */ + byte continuationOpcode(); + + /** + * @return Mask for the FIN bit. + */ + byte finMask(); + + /** Set if frames larger than the frame buffer are handled with local fragmentations + * @param allowFragmentation + */ + void setAllowFrameFragmentation(boolean allowFragmentation); + + /** + * @param flags The flags bytes of a frame + * @return True of the flags indicate a final frame. + */ + boolean isMessageComplete(byte flags); + + /** + * @param opcode + * @return True if the opcode is for a control frame + */ boolean isControl(byte opcode); + + /** + * @param opcode + * @return True if the opcode is for a text frame + */ boolean isText(byte opcode); + + /** + * @param opcode + * @return True if the opcode is for a binary frame + */ boolean isBinary(byte opcode); + + /** + * @param opcode + * @return True if the opcode is for a continuation frame + */ boolean isContinuation(byte opcode); + + /** + * @param opcode + * @return True if the opcode is a close control + */ boolean isClose(byte opcode); + + /** + * @param opcode + * @return True if the opcode is a ping control + */ boolean isPing(byte opcode); + + /** + * @param opcode + * @return True if the opcode is a pong control + */ boolean isPong(byte opcode); + /** + * @return True if frames larger than the frame buffer are fragmented. + */ + boolean isAllowFrameFragmentation(); + + /** Send a control frame + * @param control + * @param data + * @param offset + * @param length + * @throws IOException + */ void sendControl(byte control,byte[] data, int offset, int length) throws IOException; + + /** Send an arbitrary frame + * @param flags + * @param opcode + * @param data + * @param offset + * @param length + * @throws IOException + */ void sendFrame(byte flags,byte opcode,byte[] data, int offset, int length) throws IOException; } - } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java index ac42f55939c..d016e67cdb6 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -21,7 +36,7 @@ import org.eclipse.jetty.io.BuffersFactory; /* ------------------------------------------------------------ */ /** The WebSocket Buffer Pool. - * + * * The normal buffers are byte array buffers so that user processes * can access directly. However the generator uses direct buffers * for the final output stage as they are filled in bulk and are more @@ -31,24 +46,23 @@ public class WebSocketBuffers { final private int _bufferSize; final private Buffers _buffers; - final private int _maxBuffers=-1; - + public WebSocketBuffers(final int bufferSize) { _bufferSize=bufferSize; - _buffers = BuffersFactory.newBuffers(Type.DIRECT,bufferSize,Type.INDIRECT,bufferSize,Type.INDIRECT,_maxBuffers); + _buffers = BuffersFactory.newBuffers(Type.DIRECT,bufferSize,Type.INDIRECT,bufferSize,Type.INDIRECT,-1); } - + public Buffer getBuffer() { return _buffers.getBuffer(); } - + public Buffer getDirectBuffer() { return _buffers.getHeader(); } - + public void returnBuffer(Buffer buffer) { _buffers.returnBuffer(buffer); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java index e3fb77a4b60..743bdc38b64 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.IOException; @@ -66,6 +81,8 @@ public class WebSocketClient private String _origin; private String _protocol; private int _maxIdleTime=-1; + private int _maxTextMessageSize=16*1024; + private int _maxBinaryMessageSize=-1; private MaskGen _maskGen; private SocketAddress _bindAddress; @@ -227,6 +244,46 @@ public class WebSocketClient _maskGen = maskGen; } + /* ------------------------------------------------------------ */ + /** + * @return The initial maximum text message size (in characters) for a connection + */ + public int getMaxTextMessageSize() + { + return _maxTextMessageSize; + } + + /* ------------------------------------------------------------ */ + /** + * Set the initial maximum text message size for a connection. This can be changed by + * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}. + * @param maxTextMessageSize The default maximum text message size (in characters) for a connection + */ + public void setMaxTextMessageSize(int maxTextMessageSize) + { + _maxTextMessageSize = maxTextMessageSize; + } + + /* ------------------------------------------------------------ */ + /** + * @return The initial maximum binary message size (in bytes) for a connection + */ + public int getMaxBinaryMessageSize() + { + return _maxBinaryMessageSize; + } + + /* ------------------------------------------------------------ */ + /** + * Set the initial maximum binary message size for a connection. This can be changed by + * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}. + * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection + */ + public void setMaxBinaryMessageSize(int maxBinaryMessageSize) + { + _maxBinaryMessageSize = maxBinaryMessageSize; + } + /* ------------------------------------------------------------ */ /** *

    Opens a websocket connection to the URI and blocks until the connection is accepted or there is an error.

    @@ -275,29 +332,37 @@ public class WebSocketClient { if (!_factory.isStarted()) throw new IllegalStateException("Factory !started"); - String scheme=uri.getScheme(); - if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))) - throw new IllegalArgumentException("Bad WebSocket scheme '"+scheme+"'"); - if ("wss".equalsIgnoreCase(scheme)) - throw new IOException("wss not supported"); + + InetSocketAddress address = toSocketAddress(uri); SocketChannel channel = SocketChannel.open(); if (_bindAddress != null) channel.socket().bind(_bindAddress); channel.socket().setTcpNoDelay(true); - int maxIdleTime = getMaxIdleTime(); - InetSocketAddress address=new InetSocketAddress(uri.getHost(),uri.getPort()); - - final WebSocketFuture holder=new WebSocketFuture(websocket,uri,_protocol,_origin,_maskGen,maxIdleTime,_cookies,_extensions,channel); + WebSocketFuture holder = new WebSocketFuture(websocket, uri, this, channel); channel.configureBlocking(false); channel.connect(address); - _factory.getSelectorManager().register( channel, holder); + _factory.getSelectorManager().register(channel, holder); return holder; } + public static InetSocketAddress toSocketAddress(URI uri) + { + String scheme = uri.getScheme(); + if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))) + throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme); + int port = uri.getPort(); + if (port == 0) + throw new IllegalArgumentException("Bad WebSocket port: " + port); + if (port < 0) + port = "ws".equals(scheme) ? 80 : 443; + + return new InetSocketAddress(uri.getHost(), port); + } + /* ------------------------------------------------------------ */ /** The Future Websocket Connection. */ @@ -305,28 +370,17 @@ public class WebSocketClient { final WebSocket _websocket; final URI _uri; - final String _protocol; - final String _origin; - final MaskGen _maskGen; - final int _maxIdleTime; - final Map _cookies; - final List _extensions; + final WebSocketClient _client; final CountDownLatch _done = new CountDownLatch(1); - ByteChannel _channel; WebSocketConnection _connection; Throwable _exception; - private WebSocketFuture(WebSocket websocket, URI uri, String protocol, String origin, MaskGen maskGen, int maxIdleTime, Map cookies,List extensions, ByteChannel channel) + private WebSocketFuture(WebSocket websocket, URI uri, WebSocketClient client, ByteChannel channel) { _websocket=websocket; _uri=uri; - _protocol=protocol; - _origin=origin; - _maskGen=maskGen; - _maxIdleTime=maxIdleTime; - _cookies=cookies; - _extensions=extensions; + _client=client; _channel=channel; } @@ -334,19 +388,25 @@ public class WebSocketClient { try { + _client.getFactory().addConnection(connection); + + connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize()); + connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize()); + + WebSocketConnection con; synchronized (this) { if (_channel!=null) _connection=connection; + con=_connection; } - if (_connection!=null) + if (con!=null) { if (_websocket instanceof WebSocket.OnFrame) - ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)connection.getConnection()); - - _websocket.onOpen(connection.getConnection()); + ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)con.getConnection()); + _websocket.onOpen(con.getConnection()); } } finally @@ -373,9 +433,9 @@ public class WebSocketClient if (channel!=null) { if (ex instanceof ProtocolException) - closeChannel(channel,WebSocketConnectionD12.CLOSE_PROTOCOL,ex.getMessage()); + closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_PROTOCOL,ex.getMessage()); else - closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,ex.getMessage()); + closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,ex.getMessage()); } } finally @@ -386,12 +446,12 @@ public class WebSocketClient public Map getCookies() { - return _cookies; + return _client.getCookies(); } public String getProtocol() { - return _protocol; + return _client.getProtocol(); } public WebSocket getWebSocket() @@ -406,19 +466,20 @@ public class WebSocketClient public int getMaxIdleTime() { - return _maxIdleTime; + return _client.getMaxIdleTime(); } public String getOrigin() { - return _origin; + return _client.getOrigin(); } public MaskGen getMaskGen() { - return _maskGen; + return _client.getMaskGen(); } + @Override public String toString() { return "[" + _uri + ","+_websocket+"]@"+hashCode(); @@ -440,7 +501,7 @@ public class WebSocketClient if (channel!=null) { - closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,"cancelled"); + closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"cancelled"); return true; } return false; @@ -500,7 +561,7 @@ public class WebSocketClient } if (channel!=null) - closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,"timeout"); + closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"timeout"); if (exception!=null) throw new ExecutionException(exception); if (connection!=null) diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java index 7861e96d9fb..4b250ab1b2d 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.EOFException; @@ -5,43 +20,52 @@ import java.io.IOException; import java.net.ProtocolException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.List; +import java.util.Map; +import java.util.Queue; import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.net.ssl.SSLEngine; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.io.nio.AsyncConnection; import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.io.nio.SslConnection; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.component.AggregateLifeCycle; -import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ThreadPool; - /* ------------------------------------------------------------ */ /** *

    WebSocketClientFactory contains the common components needed by multiple {@link WebSocketClient} instances * (for example, a {@link ThreadPool}, a {@link SelectorManager NIO selector}, etc).

    *

    WebSocketClients with different configurations should share the same factory to avoid to waste resources.

    *

    If a ThreadPool or MaskGen is passed in the constructor, then it is not added with {@link AggregateLifeCycle#addBean(Object)}, - * so it's lifecycle must be controlled externally. + * so it's lifecycle must be controlled externally. + * * @see WebSocketClient */ public class WebSocketClientFactory extends AggregateLifeCycle { private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName()); - private final static Random __random = new Random(); private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept"); - + private final Queue connections = new ConcurrentLinkedQueue(); + private final SslContextFactory _sslContextFactory = new SslContextFactory(); private final ThreadPool _threadPool; private final WebSocketClientSelector _selector; private MaskGen _maskGen; @@ -53,54 +77,67 @@ public class WebSocketClientFactory extends AggregateLifeCycle */ public WebSocketClientFactory() { - _threadPool=new QueuedThreadPool(); - addBean(_threadPool); - _buffers=new WebSocketBuffers(8*1024); - addBean(_buffers); - _maskGen=new RandomMaskGen(); - addBean(_maskGen); - _selector=new WebSocketClientSelector(); - addBean(_selector); + this(new QueuedThreadPool()); } /* ------------------------------------------------------------ */ /** *

    Creates a WebSocketClientFactory with the given ThreadPool and the default configuration.

    + * * @param threadPool the ThreadPool instance to use */ public WebSocketClientFactory(ThreadPool threadPool) { - _threadPool=threadPool; - addBean(threadPool); - _buffers=new WebSocketBuffers(8*1024); - addBean(_buffers); - _maskGen=new RandomMaskGen(); - addBean(_maskGen); - _selector=new WebSocketClientSelector(); - addBean(_selector); + this(threadPool, new RandomMaskGen()); } /* ------------------------------------------------------------ */ /** - *

    Creates a WebSocketClientFactory with the specified configuration.

    + *

    Creates a WebSocketClientFactory with the given ThreadPool and the given MaskGen.

    + * * @param threadPool the ThreadPool instance to use - * @param maskGen the mask generator to use + * @param maskGen the MaskGen instance to use + */ + public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen) + { + this(threadPool, maskGen, 8192); + } + + /* ------------------------------------------------------------ */ + + /** + *

    Creates a WebSocketClientFactory with the specified configuration.

    + * + * @param threadPool the ThreadPool instance to use + * @param maskGen the mask generator to use * @param bufferSize the read buffer size */ - public WebSocketClientFactory(ThreadPool threadPool,MaskGen maskGen,int bufferSize) + public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen, int bufferSize) { - _threadPool=threadPool; + _threadPool = threadPool; addBean(threadPool); - _buffers=new WebSocketBuffers(bufferSize); + _buffers = new WebSocketBuffers(bufferSize); addBean(_buffers); - _maskGen=maskGen; - _selector=new WebSocketClientSelector(); + _maskGen = maskGen; + addBean(_maskGen); + _selector = new WebSocketClientSelector(); addBean(_selector); + addBean(_sslContextFactory); + } + + /* ------------------------------------------------------------ */ + /** + * @return the SslContextFactory used to configure SSL parameters + */ + public SslContextFactory getSslContextFactory() + { + return _sslContextFactory; } /* ------------------------------------------------------------ */ /** * Get the selectorManager. Used to configure the manager. + * * @return The {@link SelectorManager} instance. */ public SelectorManager getSelectorManager() @@ -109,8 +146,10 @@ public class WebSocketClientFactory extends AggregateLifeCycle } /* ------------------------------------------------------------ */ - /** Get the ThreadPool. + /** + * Get the ThreadPool. * Used to set/query the thread pool configuration. + * * @return The {@link ThreadPool} */ public ThreadPool getThreadPool() @@ -121,7 +160,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle /* ------------------------------------------------------------ */ /** * @return the shared mask generator, or null if no shared mask generator is used - * @see {@link WebSocketClient#getMaskGen()} + * @see WebSocketClient#getMaskGen() */ public MaskGen getMaskGen() { @@ -131,15 +170,15 @@ public class WebSocketClientFactory extends AggregateLifeCycle /* ------------------------------------------------------------ */ /** * @param maskGen the shared mask generator, or null if no shared mask generator is used - * @see {@link WebSocketClient#setMaskGen(MaskGen)} + * @see WebSocketClient#setMaskGen(MaskGen) */ public void setMaskGen(MaskGen maskGen) { if (isRunning()) throw new IllegalStateException(getState()); - if (removeBean(_maskGen)) - addBean(maskGen); - _maskGen=maskGen; + removeBean(_maskGen); + _maskGen = maskGen; + addBean(maskGen); } /* ------------------------------------------------------------ */ @@ -152,7 +191,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle if (isRunning()) throw new IllegalStateException(getState()); removeBean(_buffers); - _buffers=new WebSocketBuffers(bufferSize); + _buffers = new WebSocketBuffers(bufferSize); addBean(_buffers); } @@ -165,6 +204,12 @@ public class WebSocketClientFactory extends AggregateLifeCycle return _buffers.getBufferSize(); } + @Override + protected void doStop() throws Exception + { + closeConnections(); + } + /* ------------------------------------------------------------ */ /** *

    Creates and returns a new instance of a {@link WebSocketClient}, configured with this @@ -177,24 +222,44 @@ public class WebSocketClientFactory extends AggregateLifeCycle return new WebSocketClient(this); } - /* ------------------------------------------------------------ */ - @Override - protected void doStart() throws Exception + protected SSLEngine newSslEngine(SocketChannel channel) throws IOException { - super.doStart(); - if (getThreadPool() instanceof LifeCycle && !((LifeCycle)getThreadPool()).isStarted()) - ((LifeCycle)getThreadPool()).start(); + SSLEngine sslEngine; + if (channel != null) + { + String peerHost = channel.socket().getInetAddress().getHostAddress(); + int peerPort = channel.socket().getPort(); + sslEngine = _sslContextFactory.newSslEngine(peerHost, peerPort); + } + else + { + sslEngine = _sslContextFactory.newSslEngine(); + } + sslEngine.setUseClientMode(true); + sslEngine.beginHandshake(); + + return sslEngine; + } + + protected boolean addConnection(WebSocketConnection connection) + { + return isRunning() && connections.add(connection); + } + + protected boolean removeConnection(WebSocketConnection connection) + { + return connections.remove(connection); + } + + protected void closeConnections() + { + for (WebSocketConnection connection : connections) + connection.shutdown(); } /* ------------------------------------------------------------ */ - @Override - protected void doStop() throws Exception - { - super.doStop(); - } - - /* ------------------------------------------------------------ */ - /** WebSocket Client Selector Manager + /** + * WebSocket Client Selector Manager */ class WebSocketClientSelector extends SelectorManager { @@ -205,16 +270,35 @@ public class WebSocketClientFactory extends AggregateLifeCycle } @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, final SelectionKey sKey) throws IOException + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, final SelectionKey key) throws IOException { - return new SelectChannelEndPoint(channel,selectSet,sKey); + WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)key.attachment(); + int maxIdleTime = holder.getMaxIdleTime(); + if (maxIdleTime < 0) + maxIdleTime = (int)getMaxIdleTime(); + SelectChannelEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTime); + AsyncEndPoint endPoint = result; + + // Detect if it is SSL, and wrap the connection if so + if ("wss".equals(holder.getURI().getScheme())) + { + SSLEngine sslEngine = newSslEngine(channel); + SslConnection sslConnection = new SslConnection(sslEngine, endPoint); + endPoint.setConnection(sslConnection); + endPoint = sslConnection.getSslEndPoint(); + } + + AsyncConnection connection = selectSet.getManager().newConnection(channel, endPoint, holder); + endPoint.setConnection(connection); + + return result; } @Override - protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) { - WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture) endpoint.getSelectionKey().attachment(); - return new HandshakeConnection(endpoint,holder); + WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)attachment; + return new HandshakeConnection(endpoint, holder); } @Override @@ -226,20 +310,20 @@ public class WebSocketClientFactory extends AggregateLifeCycle @Override protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection) { - throw new IllegalStateException(); + LOG.debug("upgrade {} -> {}", oldConnection, endpoint.getConnection()); } @Override protected void endPointClosed(SelectChannelEndPoint endpoint) { - endpoint.getConnection().closed(); + endpoint.getConnection().onClose(); } @Override protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) { if (!(attachment instanceof WebSocketClient.WebSocketFuture)) - super.connectionFailed(channel,ex,attachment); + super.connectionFailed(channel, ex, attachment); else { __log.debug(ex); @@ -250,42 +334,40 @@ public class WebSocketClientFactory extends AggregateLifeCycle } } - /* ------------------------------------------------------------ */ - /** Handshake Connection. + /** + * Handshake Connection. * Handles the connection until the handshake succeeds or fails. */ - class HandshakeConnection extends AbstractConnection + class HandshakeConnection extends AbstractConnection implements AsyncConnection { - private final SelectChannelEndPoint _endp; - private final WebSocketClient.WebSocketFuture _holder; + private final AsyncEndPoint _endp; + private final WebSocketClient.WebSocketFuture _future; private final String _key; private final HttpParser _parser; private String _accept; private String _error; + private boolean _handshaken; - public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketClient.WebSocketFuture future) + public HandshakeConnection(AsyncEndPoint endpoint, WebSocketClient.WebSocketFuture future) { - super(endpoint,System.currentTimeMillis()); - _endp=endpoint; - _holder=future; + super(endpoint, System.currentTimeMillis()); + _endp = endpoint; + _future = future; - byte[] bytes=new byte[16]; - __random.nextBytes(bytes); - _key=new String(B64Code.encode(bytes)); + byte[] bytes = new byte[16]; + new Random().nextBytes(bytes); + _key = new String(B64Code.encode(bytes)); - - Buffers buffers = new SimpleBuffers(_buffers.getBuffer(),null); - _parser=new HttpParser(buffers,_endp, - - new HttpParser.EventHandler() + Buffers buffers = new SimpleBuffers(_buffers.getBuffer(), null); + _parser = new HttpParser(buffers, _endp, new HttpParser.EventHandler() { @Override public void startResponse(Buffer version, int status, Buffer reason) throws IOException { - if (status!=101) + if (status != 101) { - _error="Bad response status "+status+" "+reason; + _error = "Bad response status " + status + " " + reason; _endp.close(); } } @@ -294,103 +376,121 @@ public class WebSocketClientFactory extends AggregateLifeCycle public void parsedHeader(Buffer name, Buffer value) throws IOException { if (__ACCEPT.equals(name)) - _accept=value.toString(); + _accept = value.toString(); } @Override public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException { - if (_error==null) - _error="Bad response: "+method+" "+url+" "+version; + if (_error == null) + _error = "Bad response: " + method + " " + url + " " + version; _endp.close(); } @Override public void content(Buffer ref) throws IOException { - if (_error==null) - _error="Bad response. "+ref.length()+"B of content?"; + if (_error == null) + _error = "Bad response. " + ref.length() + "B of content?"; _endp.close(); } }); + } - String path=_holder.getURI().getPath(); - if (path==null || path.length()==0) - path="/"; + private void handshake() + { + String path = _future.getURI().getPath(); + if (path == null || path.length() == 0) + path = "/"; - String origin = future.getOrigin(); + if (_future.getURI().getRawQuery() != null) + path += "?" + _future.getURI().getRawQuery(); - String request= - "GET "+path+" HTTP/1.1\r\n"+ - "Host: "+future.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+ - "Upgrade: websocket\r\n"+ - "Connection: Upgrade\r\n"+ - "Sec-WebSocket-Key: "+_key+"\r\n"+ - (origin==null?"":"Origin: "+origin+"\r\n")+ - "Sec-WebSocket-Version: "+WebSocketConnectionD12.VERSION+"\r\n"; + String origin = _future.getOrigin(); - if (future.getProtocol()!=null) - request+="Sec-WebSocket-Protocol: "+future.getProtocol()+"\r\n"; + StringBuilder request = new StringBuilder(512); + request.append("GET ").append(path).append(" HTTP/1.1\r\n") + .append("Host: ").append(_future.getURI().getHost()).append(":") + .append(_future.getURI().getPort()).append("\r\n") + .append("Upgrade: websocket\r\n") + .append("Connection: Upgrade\r\n") + .append("Sec-WebSocket-Key: ") + .append(_key).append("\r\n"); - if (future.getCookies()!=null && future.getCookies().size()>0) + if (origin != null) + request.append("Origin: ").append(origin).append("\r\n"); + + request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n"); + + if (_future.getProtocol() != null) + request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n"); + + Map cookies = _future.getCookies(); + if (cookies != null && cookies.size() > 0) { - for (String cookie : future.getCookies().keySet()) - request+="Cookie: "+QuotedStringTokenizer.quoteIfNeeded(cookie,HttpFields.__COOKIE_DELIM)+ - "="+ - QuotedStringTokenizer.quoteIfNeeded(future.getCookies().get(cookie),HttpFields.__COOKIE_DELIM)+ - "\r\n"; + for (String cookie : cookies.keySet()) + request.append("Cookie: ") + .append(QuotedStringTokenizer.quoteIfNeeded(cookie, HttpFields.__COOKIE_DELIM)) + .append("=") + .append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), HttpFields.__COOKIE_DELIM)) + .append("\r\n"); } - request+="\r\n"; + request.append("\r\n"); // TODO extensions try { - Buffer handshake = new ByteArrayBuffer(request,false); - int len=handshake.length(); - if (len!=_endp.flush(handshake)) + Buffer handshake = new ByteArrayBuffer(request.toString(), false); + int len = handshake.length(); + if (len != _endp.flush(handshake)) throw new IOException("incomplete"); } - catch(IOException e) + catch (IOException e) { - future.handshakeFailed(e); + _future.handshakeFailed(e); + } + finally + { + _handshaken = true; } - } public Connection handle() throws IOException { while (_endp.isOpen() && !_parser.isComplete()) { - switch (_parser.parseAvailable()) + if (!_handshaken) + handshake(); + + if (!_parser.parseAvailable()) { - case -1: - _holder.handshakeFailed(new IOException("Incomplete handshake response")); - return this; - case 0: - return this; - default: - break; + if (_endp.isInputShutdown()) + _future.handshakeFailed(new IOException("Incomplete handshake response")); + return this; } } - if (_error==null) + if (_error == null) { - if (_accept==null) - _error="No Sec-WebSocket-Accept"; - else if (!WebSocketConnectionD12.hashKey(_key).equals(_accept)) - _error="Bad Sec-WebSocket-Accept"; + if (_accept == null) + { + _error = "No Sec-WebSocket-Accept"; + } + else if (!WebSocketConnectionRFC6455.hashKey(_key).equals(_accept)) + { + _error = "Bad Sec-WebSocket-Accept"; + } else { - Buffer header=_parser.getHeaderBuffer(); - MaskGen maskGen=_holder.getMaskGen(); - WebSocketConnectionD12 connection = new WebSocketConnectionD12(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10,maskGen); + WebSocketConnection connection = newWebSocketConnection(); + Buffer header = _parser.getHeaderBuffer(); if (header.hasContent()) connection.fillBuffersFrom(header); _buffers.returnBuffer(header); - _holder.onConnection(connection); + _future.onConnection(connection); return connection; } @@ -400,6 +500,26 @@ public class WebSocketClientFactory extends AggregateLifeCycle return this; } + private WebSocketConnection newWebSocketConnection() throws IOException + { + return new WebSocketClientConnection( + _future._client.getFactory(), + _future.getWebSocket(), + _endp, + _buffers, + System.currentTimeMillis(), + _future.getMaxIdleTime(), + _future.getProtocol(), + null, + WebSocketConnectionRFC6455.VERSION, + _future.getMaskGen()); + } + + public void onInputShutdown() throws IOException + { + _endp.close(); + } + public boolean isIdle() { return false; @@ -410,12 +530,30 @@ public class WebSocketClientFactory extends AggregateLifeCycle return false; } - public void closed() + public void onClose() { - if (_error!=null) - _holder.handshakeFailed(new ProtocolException(_error)); + if (_error != null) + _future.handshakeFailed(new ProtocolException(_error)); else - _holder.handshakeFailed(new EOFException()); + _future.handshakeFailed(new EOFException()); + } + } + + private static class WebSocketClientConnection extends WebSocketConnectionRFC6455 + { + private final WebSocketClientFactory factory; + + public WebSocketClientConnection(WebSocketClientFactory factory, WebSocket webSocket, EndPoint endPoint, WebSocketBuffers buffers, long timeStamp, int maxIdleTime, String protocol, List extensions, int draftVersion, MaskGen maskGen) throws IOException + { + super(webSocket, endPoint, buffers, timeStamp, maxIdleTime, protocol, extensions, draftVersion, maskGen); + this.factory = factory; + } + + @Override + public void onClose() + { + super.onClose(); + factory.removeConnection(this); } } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java index 8a4f7f78ae3..67bfb37e651 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java @@ -1,22 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import java.io.IOException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.jetty.io.Buffer; -import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.nio.AsyncConnection; -public interface WebSocketConnection extends Connection +public interface WebSocketConnection extends AsyncConnection { void fillBuffersFrom(Buffer buffer); - - void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException; - + List getExtensions(); - + WebSocket.Connection getConnection(); -} \ No newline at end of file + + void shutdown(); +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java index 0c2b2ee6423..12ec1101444 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -19,9 +34,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; @@ -29,9 +41,7 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.nio.IndirectNIOBuffer; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.WebSocket.OnFrame; @@ -43,51 +53,26 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc public final static byte LENGTH_FRAME=(byte)0x80; public final static byte SENTINEL_FRAME=(byte)0x00; - final IdleCheck _idle; - final WebSocketParser _parser; - final WebSocketGenerator _generator; - final WebSocket _websocket; - final String _protocol; - String _key1; - String _key2; - ByteArrayBuffer _hixieBytes; - + private final WebSocketParser _parser; + private final WebSocketGenerator _generator; + private final WebSocket _websocket; + private final String _protocol; + private String _key1; + private String _key2; + private ByteArrayBuffer _hixieBytes; + public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol) throws IOException { super(endpoint,timestamp); - if (endpoint instanceof AsyncEndPoint) - ((AsyncEndPoint)endpoint).cancelIdle(); - + _endp.setMaxIdleTime(maxIdleTime); - + _websocket = websocket; _protocol=protocol; _generator = new WebSocketGeneratorD00(buffers, _endp); - _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket)); - - if (_endp instanceof SelectChannelEndPoint) - { - final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp; - scep.cancelIdle(); - _idle=new IdleCheck() - { - public void access(EndPoint endp) - { - scep.scheduleIdle(); - } - }; - scep.scheduleIdle(); - } - else - { - _idle = new IdleCheck() - { - public void access(EndPoint endp) - {} - }; - } + _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket)); } /* ------------------------------------------------------------ */ @@ -112,8 +97,8 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc { // handle stupid hixie random bytes if (_hixieBytes!=null) - { - + { + // take any available bytes from the parser buffer, which may have already been read Buffer buffer=_parser.getBuffer(); if (buffer!=null && buffer.length()>0) @@ -124,7 +109,7 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc _hixieBytes.put(buffer.peek(buffer.getIndex(),l)); buffer.skip(l); } - + // while we are not blocked while(_endp.isOpen()) { @@ -154,7 +139,7 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc _websocket.onOpen(this); return this; } - + // handle the framing protocol boolean progress=true; @@ -165,11 +150,10 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc progress = flushed>0 || filled>0; - if (filled<0 || flushed<0) - { - _endp.close(); - break; - } + _endp.flush(); + + if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed()) + progress=true; } } catch(IOException e) @@ -177,7 +161,8 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc LOG.debug(e); try { - _endp.close(); + if (_endp.isOpen()) + _endp.close(); } catch(IOException e2) { @@ -189,22 +174,26 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc { if (_endp.isOpen()) { - _idle.access(_endp); - if (_endp.isInputShutdown() && _generator.isBufferEmpty()) _endp.close(); else checkWriteable(); - + checkWriteable(); } } return this; } + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + // TODO + } + /* ------------------------------------------------------------ */ private void doTheHixieHixieShake() - { + { byte[] result=WebSocketConnectionD00.doTheHixieHixieShake( WebSocketConnectionD00.hixieCrypt(_key1), WebSocketConnectionD00.hixieCrypt(_key2), @@ -232,7 +221,7 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc } /* ------------------------------------------------------------ */ - public void closed() + public void onClose() { _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,""); } @@ -246,7 +235,6 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -255,7 +243,6 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -278,7 +265,6 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0,opcode,content,offset,length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -289,6 +275,12 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ public void disconnect() + { + close(); + } + + /* ------------------------------------------------------------ */ + public void close() { try { @@ -301,6 +293,11 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc } } + public void shutdown() + { + close(); + } + /* ------------------------------------------------------------ */ public void fillBuffersFrom(Buffer buffer) { @@ -334,12 +331,12 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc } public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3) - { + { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte [] fodder = new byte[16]; - + fodder[0]=(byte)(0xff&(key1>>24)); fodder[1]=(byte)(0xff&(key1>>16)); fodder[2]=(byte)(0xff&(key1>>8)); @@ -348,11 +345,9 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc fodder[5]=(byte)(0xff&(key2>>16)); fodder[6]=(byte)(0xff&(key2>>8)); fodder[7]=(byte)(0xff&key2); - for (int i=0;i<8;i++) - fodder[8+i]=key3[i]; + System.arraycopy(key3, 0, fodder, 8, 8); md.update(fodder); - byte[] result=md.digest(); - return result; + return md.digest(); } catch (NoSuchAlgorithmException e) { @@ -360,60 +355,11 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc } } - private interface IdleCheck - { - void access(EndPoint endp); - } - - public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException - { - String uri=request.getRequestURI(); - String query=request.getQueryString(); - if (query!=null && query.length()>0) - uri+="?"+query; - String host=request.getHeader("Host"); - - String origin=request.getHeader("Sec-WebSocket-Origin"); - if (origin==null) - origin=request.getHeader("Origin"); - - String key1 = request.getHeader("Sec-WebSocket-Key1"); - - if (key1!=null) - { - String key2 = request.getHeader("Sec-WebSocket-Key2"); - setHixieKeys(key1,key2); - - response.setHeader("Upgrade","WebSocket"); - response.addHeader("Connection","Upgrade"); - if (origin!=null) - response.addHeader("Sec-WebSocket-Origin",origin); - response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri); - if (subprotocol!=null) - response.addHeader("Sec-WebSocket-Protocol",subprotocol); - response.sendError(101,"WebSocket Protocol Handshake"); - } - else - { - response.setHeader("Upgrade","WebSocket"); - response.addHeader("Connection","Upgrade"); - response.addHeader("WebSocket-Origin",origin); - response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri); - if (subprotocol!=null) - response.addHeader("WebSocket-Protocol",subprotocol); - response.sendError(101,"Web Socket Protocol Handshake"); - response.flushBuffer(); - if (_websocket instanceof OnFrame) - ((OnFrame)_websocket).onHandshake(this); - _websocket.onOpen(this); - } - } - public void setMaxTextMessageSize(int size) { } - public void setMaxIdleTime(int ms) + public void setMaxIdleTime(int ms) { try { @@ -424,7 +370,7 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc LOG.warn(e); } } - + public void setMaxBinaryMessageSize(int size) { } @@ -434,6 +380,11 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc return -1; } + public int getMaxIdleTime() + { + return _endp.getMaxIdleTime(); + } + public int getMaxBinaryMessageSize() { return -1; @@ -443,23 +394,35 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc { return _protocol; } - - class FrameHandlerD0 implements WebSocketParser.FrameHandler + + protected void onFrameHandshake() + { + if (_websocket instanceof OnFrame) + { + ((OnFrame)_websocket).onHandshake(this); + } + } + + protected void onWebsocketOpen() + { + _websocket.onOpen(this); + } + + static class FrameHandlerD00 implements WebSocketParser.FrameHandler { final WebSocket _websocket; - final Utf8StringBuilder _utf8 = new Utf8StringBuilder(); - FrameHandlerD0(WebSocket websocket) + FrameHandlerD00(WebSocket websocket) { _websocket=websocket; } - + public void onFrame(byte flags, byte opcode, Buffer buffer) { try { byte[] array=buffer.array(); - + if (opcode==0) { if (_websocket instanceof WebSocket.OnTextMessage) @@ -471,19 +434,14 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length()); } } - catch(ThreadDeath th) - { - throw th; - } catch(Throwable th) { LOG.warn(th); } } - + public void close(int code,String message) { - close(code,message); } } @@ -552,15 +510,12 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc return 0; } - public void setFakeFragments(boolean fake) + public void setAllowFrameFragmentation(boolean allowFragmentation) { - // TODO Auto-generated method stub - } - public boolean isFakeFragments() + public boolean isAllowFrameFragmentation() { - // TODO Auto-generated method stub return false; } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java index a16a37057f6..c7c5f2b0a76 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -19,16 +34,12 @@ import java.security.MessageDigest; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -49,18 +60,18 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc final static byte OP_PONG = 0x03; final static byte OP_TEXT = 0x04; final static byte OP_BINARY = 0x05; - + final static int CLOSE_NORMAL=1000; final static int CLOSE_SHUTDOWN=1001; final static int CLOSE_PROTOCOL=1002; final static int CLOSE_BADDATA=1003; final static int CLOSE_LARGE=1004; - + static boolean isLastFrame(int flags) { return (flags&0x8)!=0; } - + static boolean isControlFrame(int opcode) { switch(opcode) @@ -73,10 +84,8 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc return false; } } - - + private final static byte[] MAGIC; - private final IdleCheck _idle; private final WebSocketParser _parser; private final WebSocketGenerator _generator; private final WebSocket _webSocket; @@ -85,8 +94,8 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc private final OnTextMessage _onTextMessage; private final OnControl _onControl; private final String _protocol; - private boolean _closedIn; - private boolean _closedOut; + private volatile boolean _closedIn; + private volatile boolean _closedOut; private int _maxTextMessageSize; private int _maxBinaryMessageSize=-1; @@ -101,27 +110,19 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc throw new RuntimeException(e); } } - - private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD06(); - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ + private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD06(); private final WebSocket.FrameConnection _connection = new FrameConnectionD06(); - + /* ------------------------------------------------------------ */ public WebSocketConnectionD06(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol) throws IOException { super(endpoint,timestamp); - - // TODO - can we use the endpoint idle mechanism? - if (endpoint instanceof AsyncEndPoint) - ((AsyncEndPoint)endpoint).cancelIdle(); - + _endp.setMaxIdleTime(maxIdleTime); - + _webSocket = websocket; _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null; _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null; @@ -131,30 +132,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _parser = new WebSocketParserD06(buffers, endpoint, _frameHandler,true); _protocol=protocol; - // TODO should these be AsyncEndPoint checks/calls? - if (_endp instanceof SelectChannelEndPoint) - { - final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp; - scep.cancelIdle(); - _idle=new IdleCheck() - { - public void access(EndPoint endp) - { - scep.scheduleIdle(); - } - }; - scep.scheduleIdle(); - } - else - { - _idle = new IdleCheck() - { - public void access(EndPoint endp) - {} - }; - } - - _maxTextMessageSize=buffers.getBufferSize(); + _maxTextMessageSize=buffers.getBufferSize(); _maxBinaryMessageSize=-1; } @@ -163,7 +141,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { return _connection; } - + /* ------------------------------------------------------------ */ public Connection handle() throws IOException { @@ -178,7 +156,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc int filled=_parser.parseNext(); progress = flushed>0 || filled>0; - + if (filled<0 || flushed<0) { _endp.close(); @@ -202,7 +180,6 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { if (_endp.isOpen()) { - _idle.access(_endp); if (_closedIn && _closedOut && _generator.isBufferEmpty()) _endp.close(); else if (_endp.isInputShutdown() && !_closedIn) @@ -210,11 +187,17 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc else checkWriteable(); } - + } return this; } + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + // TODO + } + /* ------------------------------------------------------------ */ public boolean isIdle() { @@ -223,7 +206,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ @Override - public void idleExpired() + public void onIdleExpired(long idleForMs) { closeOut(WebSocketConnectionD06.CLOSE_NORMAL,"Idle"); } @@ -235,7 +218,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc } /* ------------------------------------------------------------ */ - public void closed() + public void onClose() { _webSocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,""); } @@ -248,7 +231,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { if (_closedOut) _endp.close(); - else + else closeOut(code,message); } catch(IOException e) @@ -269,7 +252,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { if (_closedIn || _closedOut) _endp.close(); - else + else { if (code<=0) code=WebSocketConnectionD06.CLOSE_NORMAL; @@ -279,7 +262,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_CLOSE,bytes,0,bytes.length); } _generator.flush(); - + } catch(IOException e) { @@ -291,6 +274,13 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc } } + public void shutdown() + { + final WebSocket.Connection connection = _connection; + if (connection != null) + connection.close(CLOSE_SHUTDOWN, null); + } + /* ------------------------------------------------------------ */ public void fillBuffersFrom(Buffer buffer) { @@ -312,8 +302,19 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc return Collections.emptyList(); } - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ + protected void onFrameHandshake() + { + if (_onFrame!=null) + { + _onFrame.onHandshake(_connection); + } + } + + protected void onWebSocketOpen() + { + _webSocket.onOpen(_connection); + } + /* ------------------------------------------------------------ */ private class FrameConnectionD06 implements WebSocket.FrameConnection { @@ -330,7 +331,6 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_TEXT,data,0,data.length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -341,7 +341,6 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_BINARY,content,offset,length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -352,7 +351,6 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _generator.addFrame(flags,opcode,content,offset,length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -363,7 +361,6 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _generator.addFrame((byte)0x8,control,data,offset,length); _generator.flush(); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -388,7 +385,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc } /* ------------------------------------------------------------ */ - public void setMaxIdleTime(int ms) + public void setMaxIdleTime(int ms) { try { @@ -418,6 +415,12 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc return _maxTextMessage; } + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _endp.getMaxIdleTime(); + } + /* ------------------------------------------------------------ */ public int getMaxBinaryMessageSize() { @@ -453,7 +456,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { return 0x8; } - + /* ------------------------------------------------------------ */ public boolean isControl(byte opcode) { @@ -498,21 +501,28 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ public void disconnect() + { + close(); + } + + /* ------------------------------------------------------------ */ + public void close() { close(CLOSE_NORMAL,null); } /* ------------------------------------------------------------ */ + @Override public String toString() { return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); } - public void setFakeFragments(boolean fake) + public void setAllowFrameFragmentation(boolean allowFragmentation) { } - public boolean isFakeFragments() + public boolean isAllowFrameFragmentation() { return false; } @@ -529,14 +539,14 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc public void onFrame(byte flags, byte opcode, Buffer buffer) { - boolean lastFrame = isLastFrame(flags); - + boolean lastFrame = isLastFrame(flags); + synchronized(WebSocketConnectionD06.this) { // Ignore incoming after a close if (_closedIn) return; - + try { byte[] array=buffer.array(); @@ -547,13 +557,13 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length())) return; } - + if (_onControl!=null && isControlFrame(opcode)) { if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length())) return; } - + switch(opcode) { case WebSocketConnectionD06.OP_CONTINUATION: @@ -577,7 +587,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); _utf8.reset(); _opcode=-1; - } + } } else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0) { @@ -646,7 +656,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc // Deliver the message _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8)); } - else + else { if (_connection.getMaxTextMessageSize()>=0) { @@ -656,7 +666,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc else { _utf8.reset(); - _opcode=-1; + _opcode=-1; _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); } } @@ -673,7 +683,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc { _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length()); } - else + else { if (_connection.getMaxBinaryMessageSize()>=0) { @@ -694,13 +704,9 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc } } } - } + } } } - catch(ThreadDeath th) - { - throw th; - } catch(Throwable th) { LOG.warn(th); @@ -713,39 +719,13 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc _connection.close(code,message); } + @Override public String toString() { return WebSocketConnectionD06.this.toString()+"FH"; } } - /* ------------------------------------------------------------ */ - private interface IdleCheck - { - void access(EndPoint endp); - } - - /* ------------------------------------------------------------ */ - public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException - { - String uri=request.getRequestURI(); - String query=request.getQueryString(); - if (query!=null && query.length()>0) - uri+="?"+query; - String key = request.getHeader("Sec-WebSocket-Key"); - - response.setHeader("Upgrade","WebSocket"); - response.addHeader("Connection","Upgrade"); - response.addHeader("Sec-WebSocket-Accept",hashKey(key)); - if (subprotocol!=null) - response.addHeader("Sec-WebSocket-Protocol",subprotocol); - response.sendError(101); - - if (_onFrame!=null) - _onFrame.onHandshake(_connection); - _webSocket.onOpen(_connection); - } - /* ------------------------------------------------------------ */ public static String hashKey(String key) { diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java similarity index 81% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java index 908902ce2a2..9b282d6f6da 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -19,16 +34,12 @@ import java.security.MessageDigest; import java.util.Collections; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -39,9 +50,9 @@ import org.eclipse.jetty.websocket.WebSocket.OnControl; import org.eclipse.jetty.websocket.WebSocket.OnFrame; import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -public class WebSocketConnectionD12 extends AbstractConnection implements WebSocketConnection +public class WebSocketConnectionD08 extends AbstractConnection implements WebSocketConnection { - private static final Logger LOG = Log.getLogger(WebSocketConnectionD12.class); + private static final Logger LOG = Log.getLogger(WebSocketConnectionD08.class); final static byte OP_CONTINUATION = 0x00; final static byte OP_TEXT = 0x01; @@ -53,7 +64,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc final static byte OP_PING = 0x09; final static byte OP_PONG = 0x0A; final static byte OP_EXT_CTRL = 0x0B; - + final static int CLOSE_NORMAL=1000; final static int CLOSE_SHUTDOWN=1001; final static int CLOSE_PROTOCOL=1002; @@ -61,27 +72,26 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc final static int CLOSE_NOCODE=1005; final static int CLOSE_NOCLOSE=1006; final static int CLOSE_NOTUTF8=1007; - + final static int FLAG_FIN=0x8; - + final static int VERSION=8; - + static boolean isLastFrame(byte flags) { return (flags&FLAG_FIN)!=0; } - + static boolean isControlFrame(byte opcode) { return (opcode&OP_CONTROL)!=0; } - + private final static byte[] MAGIC; - private final IdleCheck _idle; private final List _extensions; - private final WebSocketParserD12 _parser; + private final WebSocketParserD08 _parser; private final WebSocketParser.FrameHandler _inbound; - private final WebSocketGeneratorD12 _generator; + private final WebSocketGeneratorD08 _generator; private final WebSocketGenerator _outbound; private final WebSocket _webSocket; private final OnFrame _onFrame; @@ -95,9 +105,8 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc private volatile String _closeMessage; private volatile boolean _closedIn; private volatile boolean _closedOut; - private int _maxTextMessageSize; + private int _maxTextMessageSize=-1; private int _maxBinaryMessageSize=-1; - static { @@ -110,43 +119,36 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc throw new RuntimeException(e); } } - + private final WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler(); - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ private final WebSocket.FrameConnection _connection = new WSFrameConnection(); - + /* ------------------------------------------------------------ */ - public WebSocketConnectionD12(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft) + public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft) throws IOException { this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null); } - + /* ------------------------------------------------------------ */ - public WebSocketConnectionD12(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft, MaskGen maskgen) + public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft, MaskGen maskgen) throws IOException { super(endpoint,timestamp); - + _context=Thread.currentThread().getContextClassLoader(); - - if (endpoint instanceof AsyncEndPoint) - ((AsyncEndPoint)endpoint).cancelIdle(); - + _draft=draft; _endp.setMaxIdleTime(maxIdleTime); - + _webSocket = websocket; _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null; _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null; _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null; _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null; - _generator = new WebSocketGeneratorD12(buffers, _endp,maskgen); - + _generator = new WebSocketGeneratorD08(buffers, _endp,maskgen); + _extensions=extensions; if (_extensions!=null) { @@ -163,36 +165,11 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1); _inbound=(_extensions==null||_extensions.size()==0)?_frameHandler:extensions.get(0); - - _parser = new WebSocketParserD12(buffers, endpoint,_inbound,maskgen==null); - + + _parser = new WebSocketParserD08(buffers, endpoint,_inbound,maskgen==null); + _protocol=protocol; - // TODO should these be AsyncEndPoint checks/calls? - if (_endp instanceof SelectChannelEndPoint) - { - final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp; - scep.cancelIdle(); - _idle=new IdleCheck() - { - public void access(EndPoint endp) - { - scep.scheduleIdle(); - } - }; - scep.scheduleIdle(); - } - else - { - _idle = new IdleCheck() - { - public void access(EndPoint endp) - {} - }; - } - - _maxTextMessageSize=buffers.getBufferSize(); - _maxBinaryMessageSize=-1; } /* ------------------------------------------------------------ */ @@ -200,16 +177,16 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { return _connection; } - + /* ------------------------------------------------------------ */ public List getExtensions() { if (_extensions==null) return Collections.emptyList(); - + return _extensions; } - + /* ------------------------------------------------------------ */ public Connection handle() throws IOException { @@ -227,7 +204,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc int filled=_parser.parseNext(); progress = flushed>0 || filled>0; - + if (filled<0 || flushed<0) { _endp.close(); @@ -254,7 +231,6 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc _generator.returnBuffer(); if (_endp.isOpen()) { - _idle.access(_endp); if (_closedIn && _closedOut && _outbound.isBufferEmpty()) _endp.close(); else if (_endp.isInputShutdown() && !_closedIn) @@ -266,6 +242,12 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc return this; } + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + // TODO + } + /* ------------------------------------------------------------ */ public boolean isIdle() { @@ -274,10 +256,9 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ @Override - public void idleExpired() + public void onIdleExpired(long idleForMs) { - long idle = System.currentTimeMillis()-((SelectChannelEndPoint)_endp).getIdleTimestamp(); - closeOut(WebSocketConnectionD12.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms"); + closeOut(WebSocketConnectionD08.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms"); } /* ------------------------------------------------------------ */ @@ -287,24 +268,24 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } /* ------------------------------------------------------------ */ - public void closed() + public void onClose() { final boolean closed; synchronized (this) { closed=_closeCode==0; if (closed) - _closeCode=WebSocketConnectionD12.CLOSE_NOCLOSE; + _closeCode=WebSocketConnectionD08.CLOSE_NOCLOSE; } if (closed) - _webSocket.onClose(WebSocketConnectionD12.CLOSE_NOCLOSE,"closed"); + _webSocket.onClose(WebSocketConnectionD08.CLOSE_NOCLOSE,"closed"); } /* ------------------------------------------------------------ */ public void closeIn(int code,String message) { LOG.debug("ClosedIn {} {}",this,message); - + final boolean closedOut; final boolean closed; synchronized (this) @@ -330,7 +311,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { if (closedOut) _endp.close(); - else + else closeOut(code,message); } catch(IOException e) @@ -344,7 +325,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc public void closeOut(int code,String message) { LOG.debug("ClosedOut {} {}",this,message); - + final boolean close; final boolean closed; synchronized (this) @@ -358,7 +339,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc _closeMessage=message; } } - + try { if (closed) @@ -370,14 +351,14 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { if (close) _endp.close(); - else + else { if (code<=0) - code=WebSocketConnectionD12.CLOSE_NORMAL; + code=WebSocketConnectionD08.CLOSE_NORMAL; byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1); bytes[0]=(byte)(code/0x100); bytes[1]=(byte)(code%0x100); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_CLOSE,bytes,0,bytes.length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_CLOSE,bytes,0,bytes.length); } _outbound.flush(); @@ -389,6 +370,13 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } } + public void shutdown() + { + final WebSocket.Connection connection = _connection; + if (connection != null) + connection.close(CLOSE_SHUTDOWN, null); + } + /* ------------------------------------------------------------ */ public void fillBuffersFrom(Buffer buffer) { @@ -404,14 +392,23 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } } - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ + protected void onFrameHandshake() + { + if (_onFrame != null) + { + _onFrame.onHandshake(_connection); + } + } + + protected void onWebSocketOpen() + { + _webSocket.onOpen(_connection); + } + /* ------------------------------------------------------------ */ private class WSFrameConnection implements WebSocket.FrameConnection { volatile boolean _disconnecting; - int _maxTextMessage=WebSocketConnectionD12.this._maxTextMessageSize; - int _maxBinaryMessage=WebSocketConnectionD12.this._maxBinaryMessageSize; /* ------------------------------------------------------------ */ public void sendMessage(String content) throws IOException @@ -419,9 +416,8 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (_closedOut) throw new IOException("closedOut "+_closeCode+":"+_closeMessage); byte[] data = content.getBytes(StringUtil.__UTF8); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_TEXT,data,0,data.length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_TEXT,data,0,data.length); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -429,9 +425,8 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { if (_closedOut) throw new IOException("closedOut "+_closeCode+":"+_closeMessage); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_BINARY,content,offset,length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_BINARY,content,offset,length); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -441,7 +436,6 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc throw new IOException("closedOut "+_closeCode+":"+_closeMessage); _outbound.addFrame(flags,opcode,content,offset,length); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -451,7 +445,6 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc throw new IOException("closedOut "+_closeCode+":"+_closeMessage); _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length); checkWriteable(); - _idle.access(_endp); } /* ------------------------------------------------------------ */ @@ -472,11 +465,11 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (_disconnecting) return; _disconnecting=true; - WebSocketConnectionD12.this.closeOut(code,message); + WebSocketConnectionD08.this.closeOut(code,message); } /* ------------------------------------------------------------ */ - public void setMaxIdleTime(int ms) + public void setMaxIdleTime(int ms) { try { @@ -491,25 +484,31 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ public void setMaxTextMessageSize(int size) { - _maxTextMessage=size; + _maxTextMessageSize=size; } /* ------------------------------------------------------------ */ public void setMaxBinaryMessageSize(int size) { - _maxBinaryMessage=size; + _maxBinaryMessageSize=size; + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _endp.getMaxIdleTime(); } /* ------------------------------------------------------------ */ public int getMaxTextMessageSize() { - return _maxTextMessage; + return _maxTextMessageSize; } /* ------------------------------------------------------------ */ public int getMaxBinaryMessageSize() { - return _maxBinaryMessage; + return _maxBinaryMessageSize; } /* ------------------------------------------------------------ */ @@ -541,7 +540,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { return FLAG_FIN; } - + /* ------------------------------------------------------------ */ public boolean isControl(byte opcode) { @@ -587,17 +586,23 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ public void disconnect() { - close(CLOSE_NORMAL,null); - } - - /* ------------------------------------------------------------ */ - public void setFakeFragments(boolean fake) - { - _parser.setFakeFragments(fake); + close(); } /* ------------------------------------------------------------ */ - public boolean isFakeFragments() + public void close() + { + close(CLOSE_NORMAL,null); + } + + /* ------------------------------------------------------------ */ + public void setAllowFrameFragmentation(boolean allowFragmentation) + { + _parser.setFakeFragments(allowFragmentation); + } + + /* ------------------------------------------------------------ */ + public boolean isAllowFrameFragmentation() { return _parser.isFakeFragments(); } @@ -606,7 +611,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc @Override public String toString() { - return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); + return this.getClass().getSimpleName()+"D08@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); } } @@ -621,9 +626,9 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc public void onFrame(final byte flags, final byte opcode, final Buffer buffer) { - boolean lastFrame = isLastFrame(flags); - - synchronized(WebSocketConnectionD12.this) + boolean lastFrame = isLastFrame(flags); + + synchronized(WebSocketConnectionD08.this) { // Ignore incoming after a close if (_closedIn) @@ -648,10 +653,10 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc switch(opcode) { - case WebSocketConnectionD12.OP_CONTINUATION: + case WebSocketConnectionD08.OP_CONTINUATION: { // If text, append to the message buffer - if (_onTextMessage!=null && _opcode==WebSocketConnectionD12.OP_TEXT) + if (_onTextMessage!=null && _opcode==WebSocketConnectionD08.OP_TEXT) { if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize())) { @@ -667,7 +672,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc else textMessageTooLarge(); } - + if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0) { if (checkBinaryMessageSize(_aggregate.length(),buffer.length())) @@ -691,23 +696,23 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } break; } - case WebSocketConnectionD12.OP_PING: + case WebSocketConnectionD08.OP_PING: { LOG.debug("PING {}",this); if (!_closedOut) - _connection.sendControl(WebSocketConnectionD12.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); + _connection.sendControl(WebSocketConnectionD08.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); break; } - case WebSocketConnectionD12.OP_PONG: + case WebSocketConnectionD08.OP_PONG: { LOG.debug("PONG {}",this); break; } - case WebSocketConnectionD12.OP_CLOSE: + case WebSocketConnectionD08.OP_CLOSE: { - int code=WebSocketConnectionD12.CLOSE_NOCODE; + int code=WebSocketConnectionD08.CLOSE_NOCODE; String message=null; if (buffer.length()>=2) { @@ -719,7 +724,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc break; } - case WebSocketConnectionD12.OP_TEXT: + case WebSocketConnectionD08.OP_TEXT: { if(_onTextMessage!=null) { @@ -728,7 +733,13 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc // No size limit, so handle only final frames if (lastFrame) _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8)); + else + { + LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text frame aggregation disabled"); + } } + // append bytes to message buffer (if they fit) else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize())) { if (lastFrame) @@ -739,7 +750,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } else { - _opcode=WebSocketConnectionD12.OP_TEXT; + _opcode=WebSocketConnectionD08.OP_TEXT; } } else @@ -759,19 +770,19 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc else if (_connection.getMaxBinaryMessageSize()>=0) { _opcode=opcode; - // TODO use a growing buffer rather than a fixed one. if (_aggregate==null) _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize()); _aggregate.put(buffer); } + else + { + LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Binary frame aggregation disabled"); + } } - } + } } } - catch(ThreadDeath th) - { - throw th; - } catch(Throwable th) { LOG.warn(th); @@ -784,7 +795,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (max>0 && (bufferLen+length)>max) { LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize()); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize()); _opcode=-1; if (_aggregate!=null) _aggregate.clear(); @@ -792,11 +803,11 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } return true; } - + private void textMessageTooLarge() { LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); _opcode=-1; _utf8.reset(); @@ -812,41 +823,10 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc @Override public String toString() { - return WebSocketConnectionD12.this.toString()+"FH"; + return WebSocketConnectionD08.this.toString()+"FH"; } } - /* ------------------------------------------------------------ */ - private interface IdleCheck - { - void access(EndPoint endp); - } - - /* ------------------------------------------------------------ */ - public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException - { - String uri=request.getRequestURI(); - String query=request.getQueryString(); - if (query!=null && query.length()>0) - uri+="?"+query; - String key = request.getHeader("Sec-WebSocket-Key"); - - response.setHeader("Upgrade","WebSocket"); - response.addHeader("Connection","Upgrade"); - response.addHeader("Sec-WebSocket-Accept",hashKey(key)); - if (subprotocol!=null) - response.addHeader("Sec-WebSocket-Protocol",subprotocol); - - for(Extension ext : _extensions) - response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName()); - - response.sendError(101); - - if (_onFrame!=null) - _onFrame.onHandshake(_connection); - _webSocket.onOpen(_connection); - } - /* ------------------------------------------------------------ */ public static String hashKey(String key) { @@ -867,6 +847,6 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc @Override public String toString() { - return "WS/D"+_draft+"-"+_endp; + return String.format("WS/D%d p=%s g=%s", _draft, _parser, _generator); } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java new file mode 100644 index 00000000000..7f693751be0 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java @@ -0,0 +1,978 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ +// ======================================================================== +// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Utf8Appendable; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage; +import org.eclipse.jetty.websocket.WebSocket.OnControl; +import org.eclipse.jetty.websocket.WebSocket.OnFrame; +import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; + + +/* ------------------------------------------------------------ */ +/** + *

    + *    0                   1                   2                   3
    + *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    + *   +-+-+-+-+-------+-+-------------+-------------------------------+
    + *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
    + *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
    + *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
    + *   | |1|2|3|       |K|             |                               |
    + *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
    + *   |     Extended payload length continued, if payload len == 127  |
    + *   + - - - - - - - - - - - - - - - +-------------------------------+
    + *   |                               |Masking-key, if MASK set to 1  |
    + *   +-------------------------------+-------------------------------+
    + *   | Masking-key (continued)       |          Payload Data         |
    + *   +-------------------------------- - - - - - - - - - - - - - - - +
    + *   :                     Payload Data continued ...                :
    + *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    + *   |                     Payload Data continued ...                |
    + *   +---------------------------------------------------------------+
    + * 
    + */ +public class WebSocketConnectionRFC6455 extends AbstractConnection implements WebSocketConnection +{ + private static final Logger LOG = Log.getLogger(WebSocketConnectionRFC6455.class); + + final static byte OP_CONTINUATION = 0x00; + final static byte OP_TEXT = 0x01; + final static byte OP_BINARY = 0x02; + final static byte OP_EXT_DATA = 0x03; + + final static byte OP_CONTROL = 0x08; + final static byte OP_CLOSE = 0x08; + final static byte OP_PING = 0x09; + final static byte OP_PONG = 0x0A; + final static byte OP_EXT_CTRL = 0x0B; + + final static int CLOSE_NORMAL=1000; + final static int CLOSE_SHUTDOWN=1001; + final static int CLOSE_PROTOCOL=1002; + final static int CLOSE_BAD_DATA=1003; + final static int CLOSE_UNDEFINED=1004; + final static int CLOSE_NO_CODE=1005; + final static int CLOSE_NO_CLOSE=1006; + final static int CLOSE_BAD_PAYLOAD=1007; + final static int CLOSE_POLICY_VIOLATION=1008; + final static int CLOSE_MESSAGE_TOO_LARGE=1009; + final static int CLOSE_REQUIRED_EXTENSION=1010; + final static int CLOSE_SERVER_ERROR=1011; + final static int CLOSE_FAILED_TLS_HANDSHAKE=1015; + + final static int FLAG_FIN=0x8; + + // Per RFC 6455, section 1.3 - Opening Handshake - this version is "13" + final static int VERSION=13; + + static boolean isLastFrame(byte flags) + { + return (flags&FLAG_FIN)!=0; + } + + static boolean isControlFrame(byte opcode) + { + return (opcode&OP_CONTROL)!=0; + } + + private final static byte[] MAGIC; + private final List _extensions; + private final WebSocketParserD13 _parser; + private final WebSocketGeneratorRFC6455 _generator; + private final WebSocketGenerator _outbound; + private final WebSocket _webSocket; + private final OnFrame _onFrame; + private final OnBinaryMessage _onBinaryMessage; + private final OnTextMessage _onTextMessage; + private final OnControl _onControl; + private final String _protocol; + private final int _draft; + private final ClassLoader _context; + private volatile int _closeCode; + private volatile String _closeMessage; + private volatile boolean _closedIn; + private volatile boolean _closedOut; + private int _maxTextMessageSize=-1; + private int _maxBinaryMessageSize=-1; + + static + { + try + { + MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + private final WebSocket.FrameConnection _connection = new WSFrameConnection(); + + + /* ------------------------------------------------------------ */ + public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft) + throws IOException + { + this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null); + } + + /* ------------------------------------------------------------ */ + public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft, MaskGen maskgen) + throws IOException + { + super(endpoint,timestamp); + + _context=Thread.currentThread().getContextClassLoader(); + + _draft=draft; + _endp.setMaxIdleTime(maxIdleTime); + + _webSocket = websocket; + _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null; + _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null; + _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null; + _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null; + _generator = new WebSocketGeneratorRFC6455(buffers, _endp,maskgen); + + _extensions=extensions; + WebSocketParser.FrameHandler frameHandler = new WSFrameHandler(); + if (_extensions!=null) + { + int e=0; + for (Extension extension : _extensions) + { + extension.bind( + _connection, + e==extensions.size()-1? frameHandler :extensions.get(e+1), + e==0?_generator:extensions.get(e-1)); + e++; + } + } + + _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1); + WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0); + + _parser = new WebSocketParserD13(buffers, endpoint, inbound,maskgen==null); + + _protocol=protocol; + + } + + /* ------------------------------------------------------------ */ + public WebSocket.Connection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + public List getExtensions() + { + if (_extensions==null) + return Collections.emptyList(); + + return _extensions; + } + + /* ------------------------------------------------------------ */ + public Connection handle() throws IOException + { + Thread current = Thread.currentThread(); + ClassLoader oldcontext = current.getContextClassLoader(); + current.setContextClassLoader(_context); + try + { + // handle the framing protocol + boolean progress=true; + + while (progress) + { + int flushed=_generator.flushBuffer(); + int filled=_parser.parseNext(); + + progress = flushed>0 || filled>0; + _endp.flush(); + + if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed()) + progress=true; + } + } + catch(IOException e) + { + try + { + if (_endp.isOpen()) + _endp.close(); + } + catch(IOException e2) + { + LOG.ignore(e2); + } + throw e; + } + finally + { + current.setContextClassLoader(oldcontext); + _parser.returnBuffer(); + _generator.returnBuffer(); + if (_endp.isOpen()) + { + if (_closedIn && _closedOut && _outbound.isBufferEmpty()) + _endp.close(); + else if (_endp.isInputShutdown() && !_closedIn) + closeIn(CLOSE_NO_CLOSE,null); + else + checkWriteable(); + } + } + return this; + } + + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + if (!_closedIn) + _endp.close(); + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _parser.isBufferEmpty() && _outbound.isBufferEmpty(); + } + + /* ------------------------------------------------------------ */ + @Override + public void onIdleExpired(long idleForMs) + { + closeOut(WebSocketConnectionRFC6455.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms"); + } + + /* ------------------------------------------------------------ */ + public boolean isSuspended() + { + return false; + } + + /* ------------------------------------------------------------ */ + public void onClose() + { + final boolean closed; + synchronized (this) + { + closed=_closeCode==0; + if (closed) + _closeCode=WebSocketConnectionRFC6455.CLOSE_NO_CLOSE; + } + if (closed) + _webSocket.onClose(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"closed"); + } + + /* ------------------------------------------------------------ */ + public void closeIn(int code,String message) + { + LOG.debug("ClosedIn {} {} {}",this,code,message); + + final boolean closed_out; + final boolean tell_app; + synchronized (this) + { + closed_out=_closedOut; + _closedIn=true; + tell_app=_closeCode==0; + if (tell_app) + { + _closeCode=code; + _closeMessage=message; + } + } + + try + { + if (tell_app) + _webSocket.onClose(code,message); + } + finally + { + if (!closed_out) + closeOut(code,message); + } + } + + /* ------------------------------------------------------------ */ + public void closeOut(int code,String message) + { + LOG.debug("ClosedOut {} {} {}",this,code,message); + + final boolean closed_out; + final boolean tell_app; + synchronized (this) + { + closed_out=_closedOut; + _closedOut=true; + tell_app=_closeCode==0; + if (tell_app) + { + _closeCode=code; + _closeMessage=message; + } + } + + try + { + if (tell_app) + _webSocket.onClose(code,message); + } + finally + { + try + { + if (!closed_out) + { + // Close code 1005/1006/1015 are never to be sent as a status over + // a Close control frame. Code<-1 also means no node. + + if (code < 0 || (code == WebSocketConnectionRFC6455.CLOSE_NO_CODE) || (code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE) + || (code == WebSocketConnectionRFC6455.CLOSE_FAILED_TLS_HANDSHAKE)) + { + code = -1; + } + else if (code == 0) + { + code = WebSocketConnectionRFC6455.CLOSE_NORMAL; + } + + byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1); + bytes[0]=(byte)(code/0x100); + bytes[1]=(byte)(code%0x100); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_CLOSE,bytes,0,code>0?bytes.length:0); + _outbound.flush(); + } + } + catch(IOException e) + { + LOG.ignore(e); + } + } + } + + public void shutdown() + { + final WebSocket.Connection connection = _connection; + if (connection != null) + connection.close(CLOSE_SHUTDOWN, null); + } + + /* ------------------------------------------------------------ */ + public void fillBuffersFrom(Buffer buffer) + { + _parser.fill(buffer); + } + + /* ------------------------------------------------------------ */ + private void checkWriteable() + { + if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint) + { + ((AsyncEndPoint)_endp).scheduleWrite(); + } + } + + protected void onFrameHandshake() + { + if (_onFrame != null) + { + _onFrame.onHandshake(_connection); + } + } + + protected void onWebSocketOpen() + { + _webSocket.onOpen(_connection); + } + + /* ------------------------------------------------------------ */ + private class WSFrameConnection implements WebSocket.FrameConnection + { + private volatile boolean _disconnecting; + + /* ------------------------------------------------------------ */ + public void sendMessage(String content) throws IOException + { + if (_closedOut) + throw new IOException("closedOut "+_closeCode+":"+_closeMessage); + byte[] data = content.getBytes(StringUtil.__UTF8); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length); + checkWriteable(); + } + + /* ------------------------------------------------------------ */ + public void sendMessage(byte[] content, int offset, int length) throws IOException + { + if (_closedOut) + throw new IOException("closedOut "+_closeCode+":"+_closeMessage); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_BINARY,content,offset,length); + checkWriteable(); + } + + /* ------------------------------------------------------------ */ + public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException + { + if (_closedOut) + throw new IOException("closedOut "+_closeCode+":"+_closeMessage); + _outbound.addFrame(flags,opcode,content,offset,length); + checkWriteable(); + } + + /* ------------------------------------------------------------ */ + public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException + { + // TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented + if (_closedOut) + throw new IOException("closedOut "+_closeCode+":"+_closeMessage); + _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length); + checkWriteable(); + } + + /* ------------------------------------------------------------ */ + public boolean isMessageComplete(byte flags) + { + return isLastFrame(flags); + } + + /* ------------------------------------------------------------ */ + public boolean isOpen() + { + return _endp!=null&&_endp.isOpen(); + } + + /* ------------------------------------------------------------ */ + public void close(int code, String message) + { + if (_disconnecting) + return; + _disconnecting=true; + WebSocketConnectionRFC6455.this.closeOut(code,message); + } + + /* ------------------------------------------------------------ */ + public void setMaxIdleTime(int ms) + { + try + { + _endp.setMaxIdleTime(ms); + } + catch(IOException e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------ */ + public void setMaxTextMessageSize(int size) + { + _maxTextMessageSize=size; + } + + /* ------------------------------------------------------------ */ + public void setMaxBinaryMessageSize(int size) + { + _maxBinaryMessageSize=size; + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _endp.getMaxIdleTime(); + } + + /* ------------------------------------------------------------ */ + public int getMaxTextMessageSize() + { + return _maxTextMessageSize; + } + + /* ------------------------------------------------------------ */ + public int getMaxBinaryMessageSize() + { + return _maxBinaryMessageSize; + } + + /* ------------------------------------------------------------ */ + public String getProtocol() + { + return _protocol; + } + + /* ------------------------------------------------------------ */ + public byte binaryOpcode() + { + return OP_BINARY; + } + + /* ------------------------------------------------------------ */ + public byte textOpcode() + { + return OP_TEXT; + } + + /* ------------------------------------------------------------ */ + public byte continuationOpcode() + { + return OP_CONTINUATION; + } + + /* ------------------------------------------------------------ */ + public byte finMask() + { + return FLAG_FIN; + } + + /* ------------------------------------------------------------ */ + public boolean isControl(byte opcode) + { + return isControlFrame(opcode); + } + + /* ------------------------------------------------------------ */ + public boolean isText(byte opcode) + { + return opcode==OP_TEXT; + } + + /* ------------------------------------------------------------ */ + public boolean isBinary(byte opcode) + { + return opcode==OP_BINARY; + } + + /* ------------------------------------------------------------ */ + public boolean isContinuation(byte opcode) + { + return opcode==OP_CONTINUATION; + } + + /* ------------------------------------------------------------ */ + public boolean isClose(byte opcode) + { + return opcode==OP_CLOSE; + } + + /* ------------------------------------------------------------ */ + public boolean isPing(byte opcode) + { + return opcode==OP_PING; + } + + /* ------------------------------------------------------------ */ + public boolean isPong(byte opcode) + { + return opcode==OP_PONG; + } + + /* ------------------------------------------------------------ */ + public void disconnect() + { + close(CLOSE_NORMAL,null); + } + + /* ------------------------------------------------------------ */ + public void close() + { + close(CLOSE_NORMAL,null); + } + + /* ------------------------------------------------------------ */ + public void setAllowFrameFragmentation(boolean allowFragmentation) + { + _parser.setFakeFragments(allowFragmentation); + } + + /* ------------------------------------------------------------ */ + public boolean isAllowFrameFragmentation() + { + return _parser.isFakeFragments(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return this.getClass().getSimpleName()+"D13@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class WSFrameHandler implements WebSocketParser.FrameHandler + { + private static final int MAX_CONTROL_FRAME_PAYLOAD = 125; + private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity + private ByteArrayBuffer _aggregate; + private byte _opcode=-1; + + public void onFrame(final byte flags, final byte opcode, final Buffer buffer) + { + boolean lastFrame = isLastFrame(flags); + + synchronized(WebSocketConnectionRFC6455.this) + { + // Ignore incoming after a close + if (_closedIn) + return; + } + try + { + byte[] array=buffer.array(); + + if (isControlFrame(opcode) && buffer.length()>MAX_CONTROL_FRAME_PAYLOAD) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Control frame too large: " + buffer.length() + " > " + MAX_CONTROL_FRAME_PAYLOAD); + return; + } + + // TODO: check extensions for RSV bit(s) meanings + if ((flags&0x7)!=0) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags)); + return; + } + + // Ignore all frames after error close + if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE) + { + return; + } + + // Deliver frame if websocket is a FrameWebSocket + if (_onFrame!=null) + { + if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length())) + return; + } + + if (_onControl!=null && isControlFrame(opcode)) + { + if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length())) + return; + } + + switch(opcode) + { + case WebSocketConnectionRFC6455.OP_CONTINUATION: + { + if (_opcode==-1) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad Continuation"); + return; + } + + // If text, append to the message buffer + if (_onTextMessage!=null && _opcode==WebSocketConnectionRFC6455.OP_TEXT) + { + if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize())) + { + // If this is the last fragment, deliver the text buffer + if (lastFrame) + { + _opcode=-1; + String msg =_utf8.toString(); + _utf8.reset(); + _onTextMessage.onMessage(msg); + } + } + else + textMessageTooLarge(); + } + + if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0) + { + if (_aggregate!=null && checkBinaryMessageSize(_aggregate.length(),buffer.length())) + { + _aggregate.put(buffer); + + // If this is the last fragment, deliver + if (lastFrame && _onBinaryMessage!=null) + { + try + { + _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length()); + } + finally + { + _opcode=-1; + _aggregate.clear(); + } + } + } + } + break; + } + case WebSocketConnectionRFC6455.OP_PING: + { + LOG.debug("PING {}",this); + if (!_closedOut) + { + _connection.sendControl(WebSocketConnectionRFC6455.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); + } + break; + } + + case WebSocketConnectionRFC6455.OP_PONG: + { + LOG.debug("PONG {}",this); + break; + } + + case WebSocketConnectionRFC6455.OP_CLOSE: + { + int code=WebSocketConnectionRFC6455.CLOSE_NO_CODE; + String message=null; + if (buffer.length()>=2) + { + code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]); + + // Validate close status codes. + if (code < WebSocketConnectionRFC6455.CLOSE_NORMAL || + code == WebSocketConnectionRFC6455.CLOSE_UNDEFINED || + code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE || + code == WebSocketConnectionRFC6455.CLOSE_NO_CODE || + ( code > 1011 && code <= 2999 ) || + code >= 5000 ) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid close code " + code); + return; + } + + if (buffer.length()>2) + { + if(_utf8.append(buffer.array(),buffer.getIndex()+2,buffer.length()-2,_connection.getMaxTextMessageSize())) + { + message = _utf8.toString(); + _utf8.reset(); + } + } + } + else if(buffer.length() == 1) + { + // Invalid length. use status code 1002 (Protocol error) + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid payload length of 1"); + return; + } + closeIn(code,message); + break; + } + + case WebSocketConnectionRFC6455.OP_TEXT: + { + if (_opcode!=-1) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); + return; + } + + if(_onTextMessage!=null) + { + if (_connection.getMaxTextMessageSize()<=0) + { + // No size limit, so handle only final frames + if (lastFrame) + _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8)); + else + { + LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp); + errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled"); + } + } + // append bytes to message buffer (if they fit) + else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize())) + { + if (lastFrame) + { + String msg =_utf8.toString(); + _utf8.reset(); + _onTextMessage.onMessage(msg); + } + else + { + _opcode=WebSocketConnectionRFC6455.OP_TEXT; + } + } + else + textMessageTooLarge(); + } + break; + } + + case WebSocketConnectionRFC6455.OP_BINARY: + { + if (_opcode!=-1) + { + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); + return; + } + + if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length())) + { + if (lastFrame) + { + _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length()); + } + else if (_connection.getMaxBinaryMessageSize()>=0) + { + _opcode=opcode; + // TODO use a growing buffer rather than a fixed one. + if (_aggregate==null) + _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize()); + _aggregate.put(buffer); + } + else + { + LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp); + errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled"); + } + } + break; + } + + default: + errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode)); + break; + } + } + catch(Utf8Appendable.NotUtf8Exception notUtf8) + { + LOG.warn("NOTUTF8 - {} for {}",notUtf8,_endp, notUtf8); + LOG.debug(notUtf8); + errorClose(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,"Invalid UTF-8"); + } + catch(Throwable e) + { + LOG.warn("{} for {}",e,_endp, e); + LOG.debug(e); + errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR,"Internal Server Error: "+e); + } + } + + private void errorClose(int code, String message) + { + _connection.close(code,message); + + // Brutally drop the connection + try + { + _endp.close(); + } + catch (IOException e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + } + + private boolean checkBinaryMessageSize(int bufferLen, int length) + { + int max = _connection.getMaxBinaryMessageSize(); + if (max>0 && (bufferLen+length)>max) + { + LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp); + _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize()); + _opcode=-1; + if (_aggregate!=null) + _aggregate.clear(); + return false; + } + return true; + } + + private void textMessageTooLarge() + { + LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp); + _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); + + _opcode=-1; + _utf8.reset(); + } + + public void close(int code,String message) + { + if (code!=CLOSE_NORMAL) + LOG.warn("Close: "+code+" "+message); + _connection.close(code,message); + } + + @Override + public String toString() + { + return WebSocketConnectionRFC6455.this.toString()+"FH"; + } + } + + /* ------------------------------------------------------------ */ + public static String hashKey(String key) + { + try + { + MessageDigest md = MessageDigest.getInstance("SHA1"); + md.update(key.getBytes("UTF-8")); + md.update(MAGIC); + return new String(B64Code.encode(md.digest())); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("WS/D%d p=%s g=%s", _draft, _parser, _generator); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java index 04b3dcd824f..28dd23f2962 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ @@ -15,44 +30,50 @@ package org.eclipse.jetty.websocket; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; - +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.io.ConnectedEndPoint; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.BlockingHttpConnection; import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * Factory to create WebSocket connections */ -public class WebSocketFactory +public class WebSocketFactory extends AbstractLifeCycle { private static final Logger LOG = Log.getLogger(WebSocketFactory.class); + private final Queue connections = new ConcurrentLinkedQueue(); public interface Acceptor { /* ------------------------------------------------------------ */ /** - * @param request - * @param protocol - * @returns + *

    Factory method that applications needs to implement to return a + * {@link WebSocket} object.

    + * @param request the incoming HTTP upgrade request + * @param protocol the websocket sub protocol + * @return a new {@link WebSocket} object that will handle websocket events. */ WebSocket doWebSocketConnect(HttpServletRequest request, String protocol); /* ------------------------------------------------------------ */ - /** Check the origin of an incoming WebSocket handshake request - * @param request - * @param origin + /** + *

    Checks the origin of an incoming WebSocket handshake request.

    + * @param request the incoming HTTP upgrade request + * @param origin the origin URI * @return boolean to indicate that the origin is acceptable. */ boolean checkOrigin(HttpServletRequest request, String origin); @@ -64,10 +85,12 @@ public class WebSocketFactory _extensionClasses.put("fragment",FragmentExtension.class); _extensionClasses.put("x-deflate-frame",DeflateFrameExtension.class); } - + private final Acceptor _acceptor; private WebSocketBuffers _buffers; private int _maxIdleTime = 300000; + private int _maxTextMessageSize = 16 * 1024; + private int _maxBinaryMessageSize = -1; public WebSocketFactory(Acceptor acceptor) { @@ -80,7 +103,6 @@ public class WebSocketFactory _acceptor = acceptor; } - /** * @return A modifiable map of extension name to extension class */ @@ -88,7 +110,7 @@ public class WebSocketFactory { return _extensionClasses; } - + /** * Get the maxIdleTime. * @@ -130,6 +152,48 @@ public class WebSocketFactory _buffers = new WebSocketBuffers(bufferSize); } + /** + * @return The initial maximum text message size (in characters) for a connection + */ + public int getMaxTextMessageSize() + { + return _maxTextMessageSize; + } + + /** + * Set the initial maximum text message size for a connection. This can be changed by + * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}. + * @param maxTextMessageSize The default maximum text message size (in characters) for a connection + */ + public void setMaxTextMessageSize(int maxTextMessageSize) + { + _maxTextMessageSize = maxTextMessageSize; + } + + /** + * @return The initial maximum binary message size (in bytes) for a connection + */ + public int getMaxBinaryMessageSize() + { + return _maxBinaryMessageSize; + } + + /** + * Set the initial maximum binary message size for a connection. This can be changed by + * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}. + * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection + */ + public void setMaxBinaryMessageSize(int maxBinaryMessageSize) + { + _maxBinaryMessageSize = maxBinaryMessageSize; + } + + @Override + protected void doStop() throws Exception + { + closeConnections(); + } + /** * Upgrade the request/response to a WebSocket Connection. *

    This method will not normally return, but will instead throw a @@ -151,46 +215,75 @@ public class WebSocketFactory throw new IllegalStateException("!HTTP/1.1"); int draft = request.getIntHeader("Sec-WebSocket-Version"); - if (draft < 0) + if (draft < 0) { + // Old pre-RFC version specifications (header not present in RFC-6455) draft = request.getIntHeader("Sec-WebSocket-Draft"); - HttpConnection http = HttpConnection.getCurrentConnection(); + } + AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection(); + if (http instanceof BlockingHttpConnection) + throw new IllegalStateException("Websockets not supported on blocking connectors"); ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint(); List extensions_requested = new ArrayList(); - for (Enumeration e=request.getHeaders("Sec-WebSocket-Extensions");e.hasMoreElements();) + @SuppressWarnings("unchecked") + Enumeration e = request.getHeaders("Sec-WebSocket-Extensions"); + while (e.hasMoreElements()) { - QuotedStringTokenizer tok = new QuotedStringTokenizer((String)e.nextElement(),","); + QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),","); while (tok.hasMoreTokens()) + { extensions_requested.add(tok.nextToken()); + } } - - final WebSocketConnection connection; - final List extensions; + + final WebSocketServletConnection connection; switch (draft) { - case -1: - case 0: - extensions=Collections.emptyList(); - connection = new WebSocketConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); + case -1: // unspecified draft/version + case 0: // Old school draft/version + { + connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); break; + } + case 1: + case 2: + case 3: + case 4: + case 5: case 6: - extensions=Collections.emptyList(); - connection = new WebSocketConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); + { + connection = new WebSocketServletConnectionD06(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); break; - case 7: - case 8: - case 9: - case 10: - case 11: - case 12: - extensions= initExtensions(extensions_requested,8-WebSocketConnectionD12.OP_EXT_DATA, 16-WebSocketConnectionD12.OP_EXT_CTRL,3); - connection = new WebSocketConnectionD12(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft); + } + case 7: + case 8: + { + List extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionD08.OP_EXT_DATA, 16 - WebSocketConnectionD08.OP_EXT_CTRL, 3); + connection = new WebSocketServletConnectionD08(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft); break; + } + case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version + { + List extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3); + connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft); + break; + } default: - LOG.warn("Unsupported Websocket version: "+draft); - throw new HttpException(400, "Unsupported draft specification: " + draft); + { + LOG.warn("Unsupported Websocket version: " + draft); + // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol + // Using the examples as outlined + response.setHeader("Sec-WebSocket-Version", "13, 8, 6, 0"); + throw new HttpException(400, "Unsupported websocket version specification: " + draft); + } } + addConnection(connection); + + // Set the defaults + connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize); + connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize); + // Let the connection finish processing the handshake connection.handshake(request, response, protocol); response.flushBuffer(); @@ -200,6 +293,7 @@ public class WebSocketFactory connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer()); // Tell jetty about the new connection + LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection); request.setAttribute("org.eclipse.jetty.io.Connection", connection); } @@ -229,27 +323,38 @@ public class WebSocketFactory response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } - + // Try each requested protocol WebSocket websocket = null; - String protocol = request.getHeader("Sec-WebSocket-Protocol"); - if (protocol == null) // TODO remove once draft period is over - protocol = request.getHeader("WebSocket-Protocol"); - for (String p : parseProtocols(protocol)) + + @SuppressWarnings("unchecked") + Enumeration protocols = request.getHeaders("Sec-WebSocket-Protocol"); + String protocol=null; + while (protocol==null && protocols!=null && protocols.hasMoreElements()) { - websocket = _acceptor.doWebSocketConnect(request, p); - if (websocket != null) + String candidate = protocols.nextElement(); + for (String p : parseProtocols(candidate)) { - protocol = p; - break; + websocket = _acceptor.doWebSocketConnect(request, p); + if (websocket != null) + { + protocol = p; + break; + } } } // Did we get a websocket? if (websocket == null) { - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - return false; + // Try with no protocol + websocket = _acceptor.doWebSocketConnect(request, null); + + if (websocket==null) + { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return false; + } } // Send the upgrade @@ -259,7 +364,7 @@ public class WebSocketFactory return false; } - + public List initExtensions(List requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits) { List extensions = new ArrayList(); @@ -275,9 +380,9 @@ public class WebSocketFactory String value=nv.hasMoreTokens()?nv.nextToken().trim():null; parameters.put(name,value); } - + Extension extension = newExtension(extName); - + if (extension==null) continue; @@ -303,9 +408,23 @@ public class WebSocketFactory { LOG.warn(e); } - + return null; } - - + + protected boolean addConnection(WebSocketServletConnection connection) + { + return isRunning() && connections.add(connection); + } + + protected boolean removeConnection(WebSocketServletConnection connection) + { + return connections.remove(connection); + } + + protected void closeConnections() + { + for (WebSocketServletConnection connection : connections) + connection.shutdown(); + } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java index e3e01efa480..a2759ddec21 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java index e74725dc5e9..3e683d2803f 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ @@ -149,8 +164,6 @@ public class WebSocketGeneratorD00 implements WebSocketGenerator { while (_buffer.space()==0) { - // TODO: in case the I/O system signals write ready, but when we attempt to write we cannot - // TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout boolean ready = _endp.blockWritable(blockFor); if (!ready) throw new IOException("Write timeout"); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java index a181b8cc508..88a05f061cc 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java @@ -1,21 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; import java.io.IOException; -import java.security.SecureRandom; -import java.util.Random; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EndPoint; @@ -38,14 +51,14 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator private int _m; private boolean _opsent; private final MaskGen _maskGen; - + public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp) { _buffers=buffers; _endp=endp; _maskGen=null; } - + public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) { _buffers=buffers; @@ -56,22 +69,22 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException { // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); - + long blockFor=_endp.getMaxIdleTime(); - + if (_buffer==null) _buffer=(_maskGen!=null)?_buffers.getBuffer():_buffers.getDirectBuffer(); - + boolean last=WebSocketConnectionD06.isLastFrame(flags); opcode=(byte)(((0xf&flags)<<4)+0xf&opcode); - + int space=(_maskGen!=null)?14:10; - + do { opcode = _opsent?WebSocketConnectionD06.OP_CONTINUATION:opcode; _opsent=true; - + int payload=length; if (payload+space>_buffer.capacity()) { @@ -85,7 +98,7 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator // ensure there is space for header if (_buffer.space() <= space) expelBuffer(blockFor); - + // write mask if ((_maskGen!=null)) { @@ -100,10 +113,10 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator bufferPut(new byte[]{ opcode, (byte)0x7f, - (byte)((payload>>56)&0x7f), - (byte)((payload>>48)&0xff), - (byte)((payload>>40)&0xff), - (byte)((payload>>32)&0xff), + (byte)0, + (byte)0, + (byte)0, + (byte)0, (byte)((payload>>24)&0xff), (byte)((payload>>16)&0xff), (byte)((payload>>8)&0xff), @@ -129,7 +142,7 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator { _buffer.compact(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); - + if ((_maskGen!=null)) { for (int i=0;i 0) { @@ -169,7 +182,7 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator data[i]^=_mask[+_m++%4]; _buffer.put(data); } - + private synchronized void bufferPut(byte data) throws IOException { _buffer.put((byte)(data^_mask[+_m++%4])); @@ -212,8 +225,6 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator { while (_buffer.space()==0) { - // TODO: in case the I/O system signals write ready, but when we attempt to write we cannot - // TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout boolean ready = _endp.blockWritable(blockFor); if (!ready) throw new IOException("Write timeout"); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java similarity index 83% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java index e0d510c5e92..4c11c142209 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -27,7 +42,7 @@ import org.eclipse.jetty.io.EofException; * threads will call the addMessage methods while other * threads are flushing the generator. */ -public class WebSocketGeneratorD12 implements WebSocketGenerator +public class WebSocketGeneratorD08 implements WebSocketGenerator { final private WebSocketBuffers _buffers; final private EndPoint _endp; @@ -37,14 +52,14 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator private boolean _opsent; private final MaskGen _maskGen; - public WebSocketGeneratorD12(WebSocketBuffers buffers, EndPoint endp) + public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp) { _buffers=buffers; _endp=endp; _maskGen=null; } - - public WebSocketGeneratorD12(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) + + public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) { _buffers=buffers; _endp=endp; @@ -55,27 +70,26 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator { return _buffer; } - + public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException { // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); - + boolean mask=_maskGen!=null; - + if (_buffer==null) _buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer(); - - boolean last=WebSocketConnectionD12.isLastFrame(flags); - byte orig=opcode; - + + boolean last=WebSocketConnectionD08.isLastFrame(flags); + int space=mask?14:10; - + do { - opcode = _opsent?WebSocketConnectionD12.OP_CONTINUATION:opcode; + opcode = _opsent?WebSocketConnectionD08.OP_CONTINUATION:opcode; opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode)); _opsent=true; - + int payload=length; if (payload+space>_buffer.capacity()) { @@ -85,7 +99,7 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator } else if (last) opcode= (byte)(opcode|0x80); // Set the FIN bit - + // ensure there is space for header if (_buffer.space() <= space) { @@ -93,17 +107,17 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator if (_buffer.space() <= space) flush(); } - + // write the opcode and length if (payload>0xffff) { _buffer.put(new byte[]{ opcode, mask?(byte)0xff:(byte)0x7f, - (byte)((payload>>56)&0x7f), - (byte)((payload>>48)&0xff), - (byte)((payload>>40)&0xff), - (byte)((payload>>32)&0xff), + (byte)0, + (byte)0, + (byte)0, + (byte)0, (byte)((payload>>24)&0xff), (byte)((payload>>16)&0xff), (byte)((payload>>8)&0xff), @@ -119,7 +133,7 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator } else { - _buffer.put(new byte[]{ + _buffer.put(new byte[]{ opcode, (byte)(mask?(0x80|payload):payload)}); } @@ -132,14 +146,14 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator _buffer.put(_mask); } - + // write payload int remaining = payload; while (remaining > 0) { _buffer.compact(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); - + if (mask) { for (int i=0;i 0) { @@ -170,7 +184,7 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator } while (length>0); _opsent=!last; - + if (_buffer!=null && _buffer.length()==0) { _buffers.returnBuffer(_buffer); @@ -194,7 +208,7 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator if (_buffer==null) return 0; int result = flushBuffer(); - + if (!_endp.isBlocking()) { long now = System.currentTimeMillis(); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java new file mode 100644 index 00000000000..9b214d95bac --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ +// ======================================================================== +// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.websocket; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; + + +/* ------------------------------------------------------------ */ +/** WebSocketGenerator. + * This class generates websocket packets. + * It is fully synchronized because it is likely that async + * threads will call the addMessage methods while other + * threads are flushing the generator. + */ +public class WebSocketGeneratorRFC6455 implements WebSocketGenerator +{ + final private WebSocketBuffers _buffers; + final private EndPoint _endp; + private Buffer _buffer; + private final byte[] _mask=new byte[4]; + private int _m; + private boolean _opsent; + private final MaskGen _maskGen; + private boolean _closed; + + public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp) + { + _buffers=buffers; + _endp=endp; + _maskGen=null; + } + + public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) + { + _buffers=buffers; + _endp=endp; + _maskGen=maskGen; + } + + public synchronized Buffer getBuffer() + { + return _buffer; + } + + public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException + { + // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); + + if (_closed) + throw new EofException("Closed"); + if (opcode==WebSocketConnectionRFC6455.OP_CLOSE) + _closed=true; + + boolean mask=_maskGen!=null; + + if (_buffer==null) + _buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer(); + + boolean last=WebSocketConnectionRFC6455.isLastFrame(flags); + + int space=mask?14:10; + + do + { + opcode = _opsent?WebSocketConnectionRFC6455.OP_CONTINUATION:opcode; + opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode)); + _opsent=true; + + int payload=length; + if (payload+space>_buffer.capacity()) + { + // We must fragement, so clear FIN bit + opcode=(byte)(opcode&0x7F); // Clear the FIN bit + payload=_buffer.capacity()-space; + } + else if (last) + opcode= (byte)(opcode|0x80); // Set the FIN bit + + // ensure there is space for header + if (_buffer.space() <= space) + { + flushBuffer(); + if (_buffer.space() <= space) + flush(); + } + + // write the opcode and length + if (payload>0xffff) + { + _buffer.put(new byte[]{ + opcode, + mask?(byte)0xff:(byte)0x7f, + (byte)0, + (byte)0, + (byte)0, + (byte)0, + (byte)((payload>>24)&0xff), + (byte)((payload>>16)&0xff), + (byte)((payload>>8)&0xff), + (byte)(payload&0xff)}); + } + else if (payload >=0x7e) + { + _buffer.put(new byte[]{ + opcode, + mask?(byte)0xfe:(byte)0x7e, + (byte)(payload>>8), + (byte)(payload&0xff)}); + } + else + { + _buffer.put(new byte[]{ + opcode, + (byte)(mask?(0x80|payload):payload)}); + } + + // write mask + if (mask) + { + _maskGen.genMask(_mask); + _m=0; + _buffer.put(_mask); + } + + // write payload + int remaining = payload; + while (remaining > 0) + { + _buffer.compact(); + int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); + + if (mask) + { + for (int i=0;i 0) + { + // Gently flush the data, issuing a non-blocking write + flushBuffer(); + } + else + { + // Forcibly flush the data, issuing a blocking write + flush(); + if (remaining == 0) + { + // Gently flush the data, issuing a non-blocking write + flushBuffer(); + } + } + } + offset+=payload; + length-=payload; + } + while (length>0); + _opsent=!last; + + if (_buffer!=null && _buffer.length()==0) + { + _buffers.returnBuffer(_buffer); + _buffer=null; + } + } + + public synchronized int flushBuffer() throws IOException + { + if (!_endp.isOpen()) + throw new EofException(); + + if (_buffer!=null) + { + int flushed=_buffer.hasContent()?_endp.flush(_buffer):0; + if (_closed&&_buffer.length()==0) + _endp.shutdownOutput(); + return flushed; + } + + return 0; + } + + public synchronized int flush() throws IOException + { + if (_buffer==null) + return 0; + int result = flushBuffer(); + + if (!_endp.isBlocking()) + { + long now = System.currentTimeMillis(); + long end=now+_endp.getMaxIdleTime(); + while (_buffer.length()>0) + { + boolean ready = _endp.blockWritable(end-now); + if (!ready) + { + now = System.currentTimeMillis(); + if (now=0) - _webSocketFactory.setMaxIdleTime(_maxIdleTime); - super.doStart(); - } - - /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop() - */ - @Override - protected void doStop() throws Exception - { - super.doStop(); - _webSocketFactory=null; + return _webSocketFactory; } /* ------------------------------------------------------------ */ diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java index 6cfa474df6e..b36c2ba9186 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java index 10582a5b996..190ccde210b 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -18,6 +33,7 @@ import java.io.IOException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -51,8 +67,8 @@ public class WebSocketParserD00 implements WebSocketParser * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used. * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data * is mostly used. - * @param endp - * @param handler + * @param endp the endpoint + * @param handler the handler to notify when a parse event occurs */ public WebSocketParserD00(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler) { @@ -86,10 +102,9 @@ public class WebSocketParserD00 implements WebSocketParser if (_buffer==null) _buffer=_buffers.getBuffer(); - int total_filled=0; + int progress=0; // Loop until an datagram call back or can't fill anymore - boolean progress=true; while(true) { int length=_buffer.length(); @@ -109,14 +124,14 @@ public class WebSocketParserD00 implements WebSocketParser { int filled=_endp.isOpen()?_endp.fill(_buffer):-1; if (filled<=0) - return total_filled; - total_filled+=filled; + return progress; + progress+=filled; length=_buffer.length(); } catch(IOException e) { LOG.debug(e); - return total_filled>0?total_filled:-1; + return progress>0?progress:-1; } } @@ -148,6 +163,7 @@ public class WebSocketParserD00 implements WebSocketParser { _state=STATE_START; int l=_buffer.getIndex()-_buffer.markIndex()-1; + progress++; _handler.onFrame((byte)0,_opcode,_buffer.sliceFromMark(l)); _buffer.setMarkIndex(-1); if (_buffer.length()==0) @@ -155,7 +171,7 @@ public class WebSocketParserD00 implements WebSocketParser _buffers.returnBuffer(_buffer); _buffer=null; } - return total_filled; + return progress; } continue; @@ -176,6 +192,7 @@ public class WebSocketParserD00 implements WebSocketParser Buffer data=_buffer.sliceFromMark(_length); _buffer.skip(_length); _state=STATE_START; + progress++; _handler.onFrame((byte)0, _opcode, data); if (_buffer.length()==0) @@ -184,7 +201,7 @@ public class WebSocketParserD00 implements WebSocketParser _buffer=null; } - return total_filled; + return progress; } } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java index 38a923a2cbd..4c6d29016aa 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -31,9 +46,9 @@ import org.eclipse.jetty.util.log.Logger; public class WebSocketParserD06 implements WebSocketParser { private static final Logger LOG = Log.getLogger(WebSocketParserD06.class); - - public enum State { - + + public enum State { + START(0), MASK(4), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), DATA(0), SKIP(1); int _needs; @@ -47,7 +62,7 @@ public class WebSocketParserD06 implements WebSocketParser { return _needs; } - }; + } private final WebSocketBuffers _buffers; @@ -68,8 +83,9 @@ public class WebSocketParserD06 implements WebSocketParser * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used. * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data * is mostly used. - * @param endp - * @param handler + * @param endp the endpoint + * @param handler the handler to notify when a parse event occurs + * @param masked whether masking should be handled */ public WebSocketParserD06(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean masked) { @@ -140,7 +156,7 @@ public class WebSocketParserD06 implements WebSocketParser } // if we are here, then we have sufficient bytes to process the current state. - + // Parse the buffer byte by byte (unless it is STATE_DATA) byte b; while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded)) @@ -151,7 +167,7 @@ public class WebSocketParserD06 implements WebSocketParser _state=_masked?State.MASK:State.OPCODE; _bytesNeeded=_state.getNeeds(); continue; - + case MASK: _buffer.get(_mask,0,4); available-=4; @@ -159,7 +175,7 @@ public class WebSocketParserD06 implements WebSocketParser _bytesNeeded=_state.getNeeds(); _m=0; continue; - + case OPCODE: b=_buffer.get(); available--; @@ -167,7 +183,7 @@ public class WebSocketParserD06 implements WebSocketParser b^=_mask[_m++%4]; _opcode=(byte)(b&0xf); _flags=(byte)(0xf&(b>>4)); - + if (WebSocketConnectionD06.isControlFrame(_opcode)&&!WebSocketConnectionD06.isLastFrame(_flags)) { _state=State.SKIP; @@ -200,7 +216,7 @@ public class WebSocketParserD06 implements WebSocketParser default: _length=(0x7f&b); _bytesNeeded=(int)_length; - _state=State.DATA; + _state=State.DATA; } continue; @@ -247,7 +263,7 @@ public class WebSocketParserD06 implements WebSocketParser } } continue; - + case SKIP: int skip=Math.min(available,_bytesNeeded); _buffer.skip(skip); @@ -255,7 +271,7 @@ public class WebSocketParserD06 implements WebSocketParser _bytesNeeded-=skip; if (_bytesNeeded==0) _state=State.START; - + } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java similarity index 87% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java index 1ab7f982de8..8101e02f9f6 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; @@ -28,12 +43,12 @@ import org.eclipse.jetty.util.log.Logger; * Parser the WebSocket protocol. * */ -public class WebSocketParserD12 implements WebSocketParser +public class WebSocketParserD08 implements WebSocketParser { - private static final Logger LOG = Log.getLogger(WebSocketParserD12.class); - - public enum State { - + private static final Logger LOG = Log.getLogger(WebSocketParserD08.class); + + public enum State { + START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1); int _needs; @@ -47,7 +62,7 @@ public class WebSocketParserD12 implements WebSocketParser { return _needs; } - }; + } private final WebSocketBuffers _buffers; private final EndPoint _endp; @@ -70,10 +85,11 @@ public class WebSocketParserD12 implements WebSocketParser * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used. * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data * is mostly used. - * @param endp - * @param handler + * @param endp the endpoint + * @param handler the handler to notify when a parse event occurs + * @param shouldBeMasked whether masking should be handled */ - public WebSocketParserD12(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked) + public WebSocketParserD08(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked) { _buffers=buffers; _endp=endp; @@ -159,11 +175,11 @@ public class WebSocketParserD12 implements WebSocketParser // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length()); events++; _bytesNeeded-=data.length(); - _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD12.FLAG_FIN)), _opcode, data); - - _opcode=WebSocketConnectionD12.OP_CONTINUATION; + _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD08.FLAG_FIN)), _opcode, data); + + _opcode=WebSocketConnectionD08.OP_CONTINUATION; } - + if (_buffer.space() == 0) throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity()); } @@ -185,7 +201,7 @@ public class WebSocketParserD12 implements WebSocketParser } // if we are here, then we have sufficient bytes to process the current state. - + // Parse the buffer byte by byte (unless it is STATE_DATA) byte b; while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded)) @@ -197,18 +213,18 @@ public class WebSocketParserD12 implements WebSocketParser _state=State.OPCODE; _bytesNeeded=_state.getNeeds(); continue; - + case OPCODE: b=_buffer.get(); available--; _opcode=(byte)(b&0xf); _flags=(byte)(0xf&(b>>4)); - - if (WebSocketConnectionD12.isControlFrame(_opcode)&&!WebSocketConnectionD12.isLastFrame(_flags)) + + if (WebSocketConnectionD08.isControlFrame(_opcode)&&!WebSocketConnectionD08.isLastFrame(_flags)) { events++; LOG.warn("Fragmented Control from "+_endp); - _handler.close(WebSocketConnectionD12.CLOSE_PROTOCOL,"Fragmented control"); + _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"Fragmented control"); _skip=true; } @@ -222,7 +238,7 @@ public class WebSocketParserD12 implements WebSocketParser available--; _masked=(b&0x80)!=0; b=(byte)(0x7f&b); - + switch(b) { case 0x7f: @@ -235,7 +251,7 @@ public class WebSocketParserD12 implements WebSocketParser break; default: _length=(0x7f&b); - _state=_masked?State.MASK:State.PAYLOAD; + _state=_masked?State.MASK:State.PAYLOAD; } _bytesNeeded=_state.getNeeds(); continue; @@ -249,11 +265,11 @@ public class WebSocketParserD12 implements WebSocketParser if (_length>_buffer.capacity() && !_fakeFragments) { events++; - _handler.close(WebSocketConnectionD12.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); + _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); _skip=true; } - _state=_masked?State.MASK:State.PAYLOAD; + _state=_masked?State.MASK:State.PAYLOAD; _bytesNeeded=_state.getNeeds(); } continue; @@ -265,14 +281,14 @@ public class WebSocketParserD12 implements WebSocketParser if (--_bytesNeeded==0) { _bytesNeeded=(int)_length; - if (_length>=_buffer.capacity()) + if (_length>=_buffer.capacity() && !_fakeFragments) { events++; - _handler.close(WebSocketConnectionD12.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); + _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); _skip=true; } - _state=_masked?State.MASK:State.PAYLOAD; + _state=_masked?State.MASK:State.PAYLOAD; _bytesNeeded=_state.getNeeds(); } continue; @@ -289,10 +305,10 @@ public class WebSocketParserD12 implements WebSocketParser _bytesNeeded=(int)_length; _state=_skip?State.SKIP:State.DATA; break; - + case DATA: break; - + case SKIP: int skip=Math.min(available,_bytesNeeded); _buffer.skip(skip); @@ -300,7 +316,7 @@ public class WebSocketParserD12 implements WebSocketParser _bytesNeeded-=skip; if (_bytesNeeded==0) _state=State.START; - + } } @@ -311,7 +327,7 @@ public class WebSocketParserD12 implements WebSocketParser _buffer.skip(_bytesNeeded); _state=State.START; events++; - _handler.close(WebSocketConnectionD12.CLOSE_PROTOCOL,"bad mask"); + _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"bad mask"); } else { @@ -345,12 +361,12 @@ public class WebSocketParserD12 implements WebSocketParser { if (_buffer==null) _buffer=_buffers.getBuffer(); - + _buffer.put(buffer); buffer.clear(); } } - + /* ------------------------------------------------------------ */ public void returnBuffer() { @@ -360,13 +376,13 @@ public class WebSocketParserD12 implements WebSocketParser _buffer=null; } } - + /* ------------------------------------------------------------ */ @Override public String toString() { Buffer buffer=_buffer; - return WebSocketParserD12.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString()); + return WebSocketParserD08.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString()); } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java new file mode 100644 index 00000000000..9979a683735 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java @@ -0,0 +1,404 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ +// ======================================================================== +// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.websocket; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + + +/* ------------------------------------------------------------ */ +/** + * Parser the WebSocket protocol. + * + */ +public class WebSocketParserD13 implements WebSocketParser +{ + private static final Logger LOG = Log.getLogger(WebSocketParserD13.class); + + public enum State { + + START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1); + + int _needs; + + State(int needs) + { + _needs=needs; + } + + int getNeeds() + { + return _needs; + } + } + + private final WebSocketBuffers _buffers; + private final EndPoint _endp; + private final FrameHandler _handler; + private final boolean _shouldBeMasked; + private State _state; + private Buffer _buffer; + private byte _flags; + private byte _opcode; + private int _bytesNeeded; + private long _length; + private boolean _masked; + private final byte[] _mask = new byte[4]; + private int _m; + private boolean _skip; + private boolean _fragmentFrames=true; + + /* ------------------------------------------------------------ */ + /** + * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used. + * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data + * is mostly used. + * @param endp the endpoint + * @param handler the handler to notify when a parse event occurs + * @param shouldBeMasked whether masking should be handled + */ + public WebSocketParserD13(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked) + { + _buffers=buffers; + _endp=endp; + _handler=handler; + _shouldBeMasked=shouldBeMasked; + _state=State.START; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if fake fragments should be created for frames larger than the buffer. + */ + public boolean isFakeFragments() + { + return _fragmentFrames; + } + + /* ------------------------------------------------------------ */ + /** + * @param fakeFragments True if fake fragments should be created for frames larger than the buffer. + */ + public void setFakeFragments(boolean fakeFragments) + { + _fragmentFrames = fakeFragments; + } + + /* ------------------------------------------------------------ */ + public boolean isBufferEmpty() + { + return _buffer==null || _buffer.length()==0; + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + return _buffer; + } + + /* ------------------------------------------------------------ */ + /** Parse to next event. + * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is + * available. Fill data from the {@link EndPoint} only as necessary. + * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates + * that no bytes were read and no messages parsed. A positive number indicates either + * the bytes filled or the messages parsed. + */ + public int parseNext() + { + if (_buffer==null) + _buffer=_buffers.getBuffer(); + + boolean progress=false; + int filled=-1; + + // Loop until a datagram call back or can't fill anymore + while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0)) + { + int available=_buffer.length(); + + // Fill buffer if we need a byte or need length + while (available<(_state==State.SKIP?1:_bytesNeeded)) + { + // compact to mark (set at start of data) + _buffer.compact(); + + // if no space, then the data is too big for buffer + if (_buffer.space() == 0) + { + // Can we send a fake frame? + if (_fragmentFrames && _state==State.DATA) + { + Buffer data =_buffer.get(4*(available/4)); + _buffer.compact(); + if (_masked) + { + if (data.array()==null) + data=_buffer.asMutableBuffer(); + byte[] array = data.array(); + final int end=data.putIndex(); + for (int i=data.getIndex();i>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length()); + _bytesNeeded-=data.length(); + progress=true; + _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data); + + _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION; + } + + if (_buffer.space() == 0) + throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity()); + } + + // catch IOExceptions (probably EOF) and try to parse what we have + try + { + filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer); + available=_buffer.length(); + // System.err.printf(">> filled %d/%d%n",filled,available); + if (filled<=0) + break; + } + catch(IOException e) + { + LOG.debug(e); + filled=-1; + break; + } + } + // Did we get enough? + if (available<(_state==State.SKIP?1:_bytesNeeded)) + break; + + // if we are here, then we have sufficient bytes to process the current state. + // Parse the buffer byte by byte (unless it is STATE_DATA) + byte b; + while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded)) + { + switch (_state) + { + case START: + _skip=false; + _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE; + _bytesNeeded=_state.getNeeds(); + continue; + + case OPCODE: + b=_buffer.get(); + available--; + _opcode=(byte)(b&0xf); + _flags=(byte)(0xf&(b>>4)); + + if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags)) + { + LOG.warn("Fragmented Control from "+_endp); + _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control"); + progress=true; + _skip=true; + } + + _state=State.LENGTH_7; + _bytesNeeded=_state.getNeeds(); + + continue; + + case LENGTH_7: + b=_buffer.get(); + available--; + _masked=(b&0x80)!=0; + b=(byte)(0x7f&b); + + switch(b) + { + case 0x7f: + _length=0; + _state=State.LENGTH_63; + break; + case 0x7e: + _length=0; + _state=State.LENGTH_16; + break; + default: + _length=(0x7f&b); + _state=_masked?State.MASK:State.PAYLOAD; + } + _bytesNeeded=_state.getNeeds(); + continue; + + case LENGTH_16: + b=_buffer.get(); + available--; + _length = _length*0x100 + (0xff&b); + if (--_bytesNeeded==0) + { + if (_length>_buffer.capacity() && !_fragmentFrames) + { + progress=true; + _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity()); + _skip=true; + } + + _state=_masked?State.MASK:State.PAYLOAD; + _bytesNeeded=_state.getNeeds(); + } + continue; + + case LENGTH_63: + b=_buffer.get(); + available--; + _length = _length*0x100 + (0xff&b); + if (--_bytesNeeded==0) + { + _bytesNeeded=(int)_length; + if (_length>=_buffer.capacity() && !_fragmentFrames) + { + progress=true; + _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity()); + _skip=true; + } + + _state=_masked?State.MASK:State.PAYLOAD; + _bytesNeeded=_state.getNeeds(); + } + continue; + + case MASK: + _buffer.get(_mask,0,4); + _m=0; + available-=4; + _state=State.PAYLOAD; + _bytesNeeded=_state.getNeeds(); + break; + + case PAYLOAD: + _bytesNeeded=(int)_length; + _state=_skip?State.SKIP:State.DATA; + break; + + case DATA: + break; + + case SKIP: + int skip=Math.min(available,_bytesNeeded); + progress=true; + _buffer.skip(skip); + available-=skip; + _bytesNeeded-=skip; + if (_bytesNeeded==0) + _state=State.START; + break; + + case SEEK_EOF: + progress=true; + _buffer.skip(available); + available=0; + break; + } + } + + if (_state==State.DATA && available>=_bytesNeeded) + { + if ( _masked!=_shouldBeMasked) + { + _buffer.skip(_bytesNeeded); + _state=State.START; + progress=true; + _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked"); + } + else + { + Buffer data =_buffer.get(_bytesNeeded); + if (_masked) + { + if (data.array()==null) + data=_buffer.asMutableBuffer(); + byte[] array = data.array(); + final int end=data.putIndex(); + for (int i=data.getIndex();i>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length()); + + progress=true; + _handler.onFrame(_flags, _opcode, data); + _bytesNeeded=0; + _state=State.START; + } + + break; + } + } + + return progress?1:filled; + } + + /* ------------------------------------------------------------ */ + public void fill(Buffer buffer) + { + if (buffer!=null && buffer.length()>0) + { + if (_buffer==null) + _buffer=_buffers.getBuffer(); + + _buffer.put(buffer); + buffer.clear(); + } + } + + /* ------------------------------------------------------------ */ + public void returnBuffer() + { + if (_buffer!=null && _buffer.length()==0) + { + _buffers.returnBuffer(_buffer); + _buffer=null; + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("%s@%x state=%s buffer=%s", + getClass().getSimpleName(), + hashCode(), + _state, + _buffer); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java index b3e9ede719c..1e765263e62 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java @@ -1,44 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * 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. + *******************************************************************************/ // ======================================================================== // Copyright (c) 2010 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket; import java.io.IOException; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * Servlet to upgrade connections to WebSocket - *

    + *

    * The request must have the correct upgrade headers, else it is * handled as a normal servlet request. - *

    + *

    * The initParameter "bufferSize" can be used to set the buffer size, * which is also the max frame byte size (default 8192). - *

    + *

    * The initParameter "maxIdleTime" can be used to set the time in ms - * that a websocket may be idle before closing (default 300,000). - * + * that a websocket may be idle before closing. + *

    + * The initParameter "maxTextMessagesSize" can be used to set the size in characters + * that a websocket may be accept before closing. + *

    + * The initParameter "maxBinaryMessagesSize" can be used to set the size in bytes + * that a websocket may be accept before closing. */ +@SuppressWarnings("serial") public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor { - WebSocketFactory _webSocketFactory; - + private final Logger LOG = Log.getLogger(getClass()); + private WebSocketFactory _webSocketFactory; + /* ------------------------------------------------------------ */ /** * @see javax.servlet.GenericServlet#init() @@ -46,11 +69,32 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF @Override public void init() throws ServletException { - String bs=getInitParameter("bufferSize"); - _webSocketFactory = new WebSocketFactory(this,bs==null?8192:Integer.parseInt(bs)); - String mit=getInitParameter("maxIdleTime"); - if (mit!=null) - _webSocketFactory.setMaxIdleTime(Integer.parseInt(mit)); + try + { + String bs = getInitParameter("bufferSize"); + _webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs)); + _webSocketFactory.start(); + + String max = getInitParameter("maxIdleTime"); + if (max != null) + _webSocketFactory.setMaxIdleTime(Integer.parseInt(max)); + + max = getInitParameter("maxTextMessageSize"); + if (max != null) + _webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max)); + + max = getInitParameter("maxBinaryMessageSize"); + if (max != null) + _webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max)); + } + catch (ServletException x) + { + throw x; + } + catch (Exception x) + { + throw new ServletException(x); + } } /* ------------------------------------------------------------ */ @@ -59,10 +103,10 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted()) + { + if (_webSocketFactory.acceptWebSocket(request, response) || response.isCommitted()) return; - super.service(request,response); + super.service(request, response); } /* ------------------------------------------------------------ */ @@ -70,7 +114,18 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF { return true; } - - - + + /* ------------------------------------------------------------ */ + @Override + public void destroy() + { + try + { + _webSocketFactory.stop(); + } + catch (Exception x) + { + LOG.ignore(x); + } + } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java new file mode 100644 index 00000000000..8e9daaff4d3 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface WebSocketServletConnection extends WebSocketConnection +{ + void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException; +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java new file mode 100644 index 00000000000..72632beddbb --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.QuotedStringTokenizer; + +public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implements WebSocketServletConnection +{ + private final WebSocketFactory factory; + + public WebSocketServletConnectionD00(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol) + throws IOException + { + super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol); + this.factory = factory; + } + + public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException + { + String uri = request.getRequestURI(); + String query = request.getQueryString(); + if (query != null && query.length() > 0) + { + uri += "?" + query; + } + uri = new HttpURI(uri).toString(); + String host = request.getHeader("Host"); + + String origin = request.getHeader("Sec-WebSocket-Origin"); + if (origin == null) + { + origin = request.getHeader("Origin"); + } + if (origin != null) + { + origin = QuotedStringTokenizer.quoteIfNeeded(origin,"\r\n"); + } + + String key1 = request.getHeader("Sec-WebSocket-Key1"); + + if (key1 != null) + { + String key2 = request.getHeader("Sec-WebSocket-Key2"); + setHixieKeys(key1,key2); + + response.setHeader("Upgrade","WebSocket"); + response.addHeader("Connection","Upgrade"); + if (origin != null) + { + response.addHeader("Sec-WebSocket-Origin",origin); + } + response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri); + if (subprotocol != null) + { + response.addHeader("Sec-WebSocket-Protocol",subprotocol); + } + response.sendError(101, "WebSocket Protocol Handshake"); + } + else + { + response.setHeader("Upgrade","WebSocket"); + response.addHeader("Connection","Upgrade"); + response.addHeader("WebSocket-Origin",origin); + response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri); + if (subprotocol != null) + { + response.addHeader("WebSocket-Protocol",subprotocol); + } + response.sendError(101,"Web Socket Protocol Handshake"); + response.flushBuffer(); + + onFrameHandshake(); + onWebsocketOpen(); + } + } + + @Override + public void onClose() + { + super.onClose(); + factory.removeConnection(this); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java new file mode 100644 index 00000000000..d75e153f414 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.EndPoint; + +public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implements WebSocketServletConnection +{ + private final WebSocketFactory factory; + + public WebSocketServletConnectionD06(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol) + throws IOException + { + super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol); + this.factory = factory; + } + + /* ------------------------------------------------------------ */ + public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException + { + String key = request.getHeader("Sec-WebSocket-Key"); + + response.setHeader("Upgrade","WebSocket"); + response.addHeader("Connection","Upgrade"); + response.addHeader("Sec-WebSocket-Accept",hashKey(key)); + if (subprotocol!=null) + { + response.addHeader("Sec-WebSocket-Protocol",subprotocol); + } + + response.sendError(101); + + onFrameHandshake(); + onWebSocketOpen(); + } + + @Override + public void onClose() + { + super.onClose(); + factory.removeConnection(this); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java new file mode 100644 index 00000000000..daa54334c33 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.EndPoint; + +public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection +{ + private final WebSocketFactory factory; + + public WebSocketServletConnectionD08(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, + List extensions, int draft) throws IOException + { + super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft); + this.factory = factory; + } + + /* ------------------------------------------------------------ */ + public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException + { + String key = request.getHeader("Sec-WebSocket-Key"); + + response.setHeader("Upgrade","WebSocket"); + response.addHeader("Connection","Upgrade"); + response.addHeader("Sec-WebSocket-Accept",hashKey(key)); + if (subprotocol != null) + { + response.addHeader("Sec-WebSocket-Protocol",subprotocol); + } + + for (Extension ext : getExtensions()) + { + response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName()); + } + + response.sendError(101); + + onFrameHandshake(); + onWebSocketOpen(); + } + + @Override + public void onClose() + { + super.onClose(); + factory.removeConnection(this); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java new file mode 100644 index 00000000000..b6e44fcbec0 --- /dev/null +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.EndPoint; + +public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection +{ + private final WebSocketFactory factory; + + public WebSocketServletConnectionRFC6455(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, + List extensions, int draft) throws IOException + { + super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft); + this.factory = factory; + } + + /* ------------------------------------------------------------ */ + public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException + { + String key = request.getHeader("Sec-WebSocket-Key"); + + response.setHeader("Upgrade","WebSocket"); + response.addHeader("Connection","Upgrade"); + response.addHeader("Sec-WebSocket-Accept",hashKey(key)); + if (subprotocol != null) + { + response.addHeader("Sec-WebSocket-Protocol",subprotocol); + } + + for (Extension ext : getExtensions()) + { + response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName()); + } + + response.sendError(101); + + onFrameHandshake(); + onWebSocketOpen(); + } + + @Override + public void onClose() + { + super.onClose(); + factory.removeConnection(this); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java index 7cddcc4f59c..3925aa0dba3 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java new file mode 100644 index 00000000000..9b4af2d27c9 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.helper.CaptureSocket; +import org.eclipse.jetty.websocket.helper.SafariD00; +import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class SafariWebsocketDraft0Test +{ + private Server server; + private WebSocketCaptureServlet servlet; + private URI serverUri; + + @BeforeClass + public static void initLogging() + { + // Configure Logging + // System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName()); + // System.setProperty("org.eclipse.jetty.LEVEL","DEBUG"); + } + + @Before + public void startServer() throws Exception + { + // Configure Server + server = new Server(0); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + servlet = new WebSocketCaptureServlet(); + context.addServlet(new ServletHolder(servlet),"/"); + + // Start Server + server.start(); + + Connector conn = server.getConnectors()[0]; + String host = conn.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = conn.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d/",host,port)); + // System.out.printf("Server URI: %s%n",serverUri); + } + + @Test + public void testSendTextMessages() throws Exception + { + SafariD00 safari = new SafariD00(serverUri); + + try + { + safari.connect(); + safari.issueHandshake(); + + // Send 5 short messages, using technique seen in Safari. + safari.sendMessage("aa-0"); // single msg + safari.sendMessage("aa-1", "aa-2", "aa-3", "aa-4"); + + // Servlet should show only 1 connection. + Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1)); + + CaptureSocket socket = servlet.captures.get(0); + Assert.assertThat("CaptureSocket",socket,notNullValue()); + Assert.assertThat("CaptureSocket.isConnected", socket.awaitConnected(1000), is(true)); + + // Give servlet time to process messages + threadSleep(1,TimeUnit.SECONDS); + + // Should have captured 5 messages. + Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5)); + } + finally + { + // System.out.println("Closing client socket"); + safari.disconnect(); + } + } + + public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException + { + long ms = TimeUnit.MILLISECONDS.convert(dur,unit); + Thread.sleep(ms); + } + + @After + public void stopServer() throws Exception + { + server.stop(); + } +} diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestClient.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java similarity index 83% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestClient.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java index d218cfec274..4387b7573d4 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestClient.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.net.InetSocketAddress; @@ -15,8 +30,6 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; /** - * @version $Revision$ $Date$ - * * This is not a general purpose websocket client. * It's only for testing the websocket server and is hardwired to a specific draft version of the protocol. */ @@ -52,6 +65,8 @@ public class TestClient implements WebSocket.OnFrame public void onOpen(Connection connection) { + if (_verbose) + System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName()); } public void onClose(int closeCode, String message) @@ -138,10 +153,10 @@ public class TestClient implements WebSocket.OnFrame { __framesSent++; byte flags= (byte)(off+len==data.length?0x8:0); - byte op=(byte)(off==0?opcode:WebSocketConnectionD12.OP_CONTINUATION); + byte op=(byte)(off==0?opcode:WebSocketConnectionRFC6455.OP_CONTINUATION); if (_verbose) - System.err.printf("%s#addFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len)); + System.err.printf("%s#sendFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len)); _connection.sendFrame(flags,op,data,off,len); @@ -156,7 +171,9 @@ public class TestClient implements WebSocket.OnFrame public void disconnect() throws Exception { if (_connection!=null) - _connection.disconnect(); + { + _connection.close(); + } } @@ -168,6 +185,7 @@ public class TestClient implements WebSocket.OnFrame System.err.println(" -p|--port PORT (default 8080)"); System.err.println(" -b|--binary"); System.err.println(" -v|--verbose"); + System.err.println(" -q|--quiet"); System.err.println(" -c|--count n (default 10)"); System.err.println(" -s|--size n (default 64)"); System.err.println(" -f|--fragment n (default 4000) "); @@ -226,6 +244,8 @@ public class TestClient implements WebSocket.OnFrame try { __start=System.currentTimeMillis(); + protocol=protocol==null?"echo":protocol; + for (int i=0;i1?"s":"")+" ---"); - System.out.println(__framesSent+" frames transmitted, "+__framesReceived+" received, "+ - __messagesSent+" messages transmitted, "+__messagesReceived+" received, "+ - "time "+duration+"ms "+ (1000L*__messagesReceived.get()/duration)+" req/s"); + System.out.printf("%d/%d frames sent/recv, %d/%d mesg sent/recv, time %dms %dm/s %.2fbps%n", + __framesSent,__framesReceived.get(), + __messagesSent,__messagesReceived.get(), + duration,(1000L*__messagesReceived.get()/duration), + 1000.0D*__messagesReceived.get()*8*size/duration/1024/1024); System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",__minDuration.get()/1000000.0,__messagesReceived.get()==0?0.0:(__totalTime.get()/__messagesReceived.get()/1000000.0),__maxDuration.get()/1000000.0); __clientFactory.stop(); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java similarity index 80% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestServer.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java index ab9743392e2..249b632e066 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/TestServer.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java @@ -1,7 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.IOException; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -42,7 +59,10 @@ public class TestServer extends Server else if ("org.ietf.websocket.test-echo-broadcast".equals(protocol) || "echo-broadcast".equals(protocol)) { _websocket = new TestEchoBroadcastWebSocket(); - + } + else if ("echo-broadcast-ping".equals(protocol)) + { + _websocket = new TestEchoBroadcastPingWebSocket(); } else if ("org.ietf.websocket.test-echo-assemble".equals(protocol) || "echo-assemble".equals(protocol)) { @@ -107,7 +127,7 @@ public class TestServer extends Server public void onOpen(Connection connection) { if (_verbose) - System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection); + System.err.printf("%s#onOpen %s %s\n",this.getClass().getSimpleName(),connection,connection.getProtocol()); } public void onHandshake(FrameConnection connection) @@ -169,7 +189,8 @@ public class TestServer extends Server try { if (!getConnection().isControl(opcode)) - getConnection().sendFrame(flags,opcode,data,offset,length); } + getConnection().sendFrame(flags,opcode,data,offset,length); + } catch (IOException e) { e.printStackTrace(); @@ -179,6 +200,61 @@ public class TestServer extends Server } } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestEchoBroadcastPingWebSocket extends TestEchoBroadcastWebSocket + { + Thread _keepAlive; // A dedicated thread is not a good way to do this + CountDownLatch _latch = new CountDownLatch(1); + + @Override + public void onHandshake(final FrameConnection connection) + { + super.onHandshake(connection); + _keepAlive=new Thread() + { + @Override + public void run() + { + try + { + while(!_latch.await(10,TimeUnit.SECONDS)) + { + System.err.println("Ping "+connection); + byte[] data = { (byte)1, (byte) 2, (byte) 3 }; + connection.sendControl(WebSocketConnectionRFC6455.OP_PING,data,0,data.length); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }; + _keepAlive.start(); + } + + + @Override + public boolean onControl(byte controlCode, byte[] data, int offset, int length) + { + if (controlCode==WebSocketConnectionRFC6455.OP_PONG) + System.err.println("Pong "+getConnection()); + return super.onControl(controlCode,data,offset,length); + } + + + @Override + public void onClose(int code, String message) + { + _latch.countDown(); + super.onClose(code,message); + } + + + } + + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class TestEchoBroadcastWebSocket extends TestWebSocket diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java index d6c1ca8de24..571a454e0da 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; import java.io.BufferedReader; @@ -6,6 +21,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URI; @@ -26,9 +42,11 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; + public class WebSocketClientTest { private WebSocketClientFactory _factory = new WebSocketClientFactory(); @@ -53,7 +71,6 @@ public class WebSocketClientTest _factory.stop(); } - @Ignore @Test public void testMessageBiggerThanBufferSize() throws Exception { @@ -73,7 +90,7 @@ public class WebSocketClientTest public void onMessage(String data) { - System.out.println("data = " + data); + // System.out.println("data = " + data); dataLatch.countDown(); } @@ -81,7 +98,7 @@ public class WebSocketClientTest { } }; - Future future = client.open(new URI("ws://127.0.0.1:" + _serverPort + "/"), websocket); + client.open(new URI("ws://127.0.0.1:" + _serverPort + "/"), websocket); Socket socket = _server.accept(); accept(socket); @@ -133,7 +150,6 @@ public class WebSocketClientTest Assert.assertFalse(open.get()); } - @Test public void testAsyncConnectionRefused() throws Exception { @@ -167,13 +183,11 @@ public class WebSocketClientTest } Assert.assertFalse(open.get()); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get()); Assert.assertTrue(error instanceof ConnectException); } - - @Test public void testConnectionNotAccepted() throws Exception { @@ -207,7 +221,7 @@ public class WebSocketClientTest } Assert.assertFalse(open.get()); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get()); Assert.assertTrue(error instanceof TimeoutException); } @@ -246,12 +260,11 @@ public class WebSocketClientTest } Assert.assertFalse(open.get()); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get()); Assert.assertTrue(error instanceof TimeoutException); } - @Test public void testBadHandshake() throws Exception { @@ -287,7 +300,7 @@ public class WebSocketClientTest } Assert.assertFalse(open.get()); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_PROTOCOL,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,close.get()); Assert.assertTrue(error instanceof IOException); Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0); @@ -330,7 +343,7 @@ public class WebSocketClientTest error=e.getCause(); } Assert.assertFalse(open.get()); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_PROTOCOL,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,close.get()); Assert.assertTrue(error instanceof IOException); Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0); } @@ -368,7 +381,7 @@ public class WebSocketClientTest socket.close(); _latch.await(10,TimeUnit.SECONDS); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get()); } @@ -406,10 +419,9 @@ public class WebSocketClientTest long start=System.currentTimeMillis(); _latch.await(10,TimeUnit.SECONDS); Assert.assertTrue(System.currentTimeMillis()-start<5000); - Assert.assertEquals(WebSocketConnectionD12.CLOSE_NORMAL,close.get()); + Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NORMAL,close.get()); } - @Test public void testNotIdle() throws Exception { @@ -420,6 +432,7 @@ public class WebSocketClientTest final AtomicInteger close = new AtomicInteger(); final CountDownLatch _latch = new CountDownLatch(1); final BlockingQueue queue = new BlockingArrayQueue(); + final StringBuilder closeMessage = new StringBuilder(); Future future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage() { public void onOpen(Connection connection) @@ -430,6 +443,7 @@ public class WebSocketClientTest public void onClose(int closeCode, String message) { close.set(closeCode); + closeMessage.append(message); _latch.countDown(); } @@ -478,11 +492,10 @@ public class WebSocketClientTest _latch.await(10,TimeUnit.SECONDS); Assert.assertTrue(System.currentTimeMillis()-start<5000); - Assert.assertEquals(1111,close.get()); - + Assert.assertEquals(1002,close.get()); + Assert.assertEquals("Invalid close code 1111", closeMessage.toString()); } - @Test public void testBlockSending() throws Exception { @@ -518,16 +531,18 @@ public class WebSocketClientTest Assert.assertTrue(open.get()); Assert.assertEquals(0,close.get()); - final int messages=20000; + final int messages=200000; final AtomicLong totalB=new AtomicLong(); Thread consumer = new Thread() { + @Override public void run() { + // Thread.sleep is for artificially poor performance reader needed for this testcase. try { - Thread.sleep(2000); + Thread.sleep(200); byte[] recv = new byte[32*1024]; int len=0; @@ -551,32 +566,28 @@ public class WebSocketClientTest consumer.start(); // Send lots of messages client to server - long max=0; long start=System.currentTimeMillis(); String mesg="This is a test message to send"; for (int i=0;imax) - max=duration; - } + connection.sendMessage(mesg); } + // Duration for the write phase + long writeDur = (System.currentTimeMillis() - start); + // wait for consumer to complete while (totalB.get()1000); // writing was blocked + } + + Assert.assertThat("write duration", writeDur, greaterThan(1000L)); // writing was blocked Assert.assertEquals(messages*(mesg.length()+6L),totalB.get()); consumer.interrupt(); } - @Test public void testBlockReceiving() throws Exception { @@ -586,6 +597,7 @@ public class WebSocketClientTest final AtomicBoolean open = new AtomicBoolean(); final AtomicInteger close = new AtomicInteger(); final CountDownLatch _latch = new CountDownLatch(1); + final StringBuilder closeMessage = new StringBuilder(); final Exchanger exchanger = new Exchanger(); Future future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage() { @@ -596,8 +608,8 @@ public class WebSocketClientTest public void onClose(int closeCode, String message) { - //System.err.println("CLOSE "+closeCode+" "+message); close.set(closeCode); + closeMessage.append(message); _latch.countDown(); } @@ -633,18 +645,28 @@ public class WebSocketClientTest // Set up a consumer of received messages that waits a while before consuming Thread consumer = new Thread() { + @Override public void run() { try { - Thread.sleep(2000); - while(m.get()max) - max=duration; - } } while(consumer.isAlive()) + { Thread.sleep(10); + } + // Duration of the read operation. + long readDur = (System.currentTimeMillis() - start); - Assert.assertTrue(max>1000); // writing was blocked + Assert.assertThat("read duration", readDur, greaterThan(1000L)); // reading was blocked Assert.assertEquals(m.get(),messages); // Close with code @@ -690,8 +706,26 @@ public class WebSocketClientTest _latch.await(10,TimeUnit.SECONDS); Assert.assertTrue(System.currentTimeMillis()-start<5000); - Assert.assertEquals(1111,close.get()); + Assert.assertEquals(1002,close.get()); + Assert.assertEquals("Invalid close code 1111", closeMessage.toString()); + } + @Test + public void testURIWithDefaultPort() throws Exception + { + URI uri = new URI("ws://localhost"); + InetSocketAddress addr = WebSocketClient.toSocketAddress(uri); + Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost")); + Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(80)); + } + + @Test + public void testURIWithDefaultWSSPort() throws Exception + { + URI uri = new URI("wss://localhost"); + InetSocketAddress addr = WebSocketClient.toSocketAddress(uri); + Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost")); + Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(443)); } private void respondToClient(Socket connection, String serverResponse) throws IOException @@ -742,7 +776,7 @@ public class WebSocketClientTest } connection.getOutputStream().write(( "HTTP/1.1 101 Upgrade\r\n" + - "Sec-WebSocket-Accept: "+ WebSocketConnectionD12.hashKey(key) +"\r\n" + + "Sec-WebSocket-Accept: "+ WebSocketConnectionRFC6455.hashKey(key) +"\r\n" + "\r\n").getBytes()); } } diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java new file mode 100644 index 00000000000..1668d13b5ad --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.helper.CaptureSocket; +import org.eclipse.jetty.websocket.helper.MessageSender; +import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +/** + * WebSocketCommTest - to test reported undelivered messages in bug JETTY-1463 + */ +public class WebSocketCommTest +{ + private Server server; + private WebSocketCaptureServlet servlet; + private URI serverUri; + + @Before + public void startServer() throws Exception + { + // Configure Server + server = new Server(0); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + servlet = new WebSocketCaptureServlet(); + context.addServlet(new ServletHolder(servlet),"/"); + + // Start Server + server.start(); + + Connector conn = server.getConnectors()[0]; + String host = conn.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = conn.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d/",host,port)); + System.out.printf("Server URI: %s%n",serverUri); + } + + @After + public void stopServer() + { + try + { + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(System.err); + } + } + + @Test + public void testSendTextMessages() throws Exception + { + WebSocketClientFactory clientFactory = new WebSocketClientFactory(); + clientFactory.start(); + + WebSocketClient wsc = clientFactory.newWebSocketClient(); + MessageSender sender = new MessageSender(); + wsc.open(serverUri,sender); + + try + { + sender.awaitConnect(); + + // Send 5 short messages + for (int i = 0; i < 5; i++) + { + System.out.printf("Sending msg-%d%n",i); + sender.sendMessage("msg-%d",i); + } + + // Servlet should show only 1 connection. + Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1)); + + CaptureSocket socket = servlet.captures.get(0); + Assert.assertThat("CaptureSocket",socket,notNullValue()); + Assert.assertThat("CaptureSocket.isConnected",socket.awaitConnected(1000),is(true)); + + // Give servlet time to process messages + TimeUnit.MILLISECONDS.sleep(500); + + // Should have captured 5 messages. + Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5)); + } + finally + { + System.out.println("Closing client socket"); + sender.close(); + } + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java index 4b1c1aded35..5d86a874c86 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java @@ -1,6 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.*; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; @@ -8,9 +23,6 @@ import org.eclipse.jetty.util.StringUtil; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketGeneratorD00Test { private ByteArrayBuffer _out; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java index f4cc5603071..e9435f875db 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java @@ -1,6 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.*; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; @@ -8,9 +23,6 @@ import org.eclipse.jetty.util.StringUtil; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketGeneratorD06Test { private ByteArrayBuffer _out; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java similarity index 83% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java index 6d3fb5fb45f..22e173b6417 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java @@ -1,6 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.*; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; @@ -8,10 +23,7 @@ import org.eclipse.jetty.util.StringUtil; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ -public class WebSocketGeneratorD12Test +public class WebSocketGeneratorD08Test { private ByteArrayBuffer _out; private WebSocketGenerator _generator; @@ -42,7 +54,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneString() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8); _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length); @@ -69,7 +81,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneBuffer() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); String string = "Hell\uFF4F W\uFF4Frld"; byte[] bytes=string.getBytes(StringUtil.__UTF8); @@ -97,7 +109,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneLongBuffer() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); byte[] b=new byte[150]; for (int i=0;i "+message); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD13Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD13Test.java new file mode 100644 index 00000000000..9a12059a006 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD13Test.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.lang.management.ManagementFactory; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; + +import junit.framework.Assert; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class WebSocketLoadD13Test +{ + private static Server _server; + private static Connector _connector; + + @BeforeClass + public static void startServer() throws Exception + { + _server = new Server(); + + _connector = new SelectChannelConnector(); + _server.addConnector(_connector); + + QueuedThreadPool threadPool = new QueuedThreadPool(200); + threadPool.setMaxStopTimeMs(1000); + _server.setThreadPool(threadPool); + + WebSocketHandler wsHandler = new WebSocketHandler() + { + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + return new EchoWebSocket(); + } + }; + wsHandler.setHandler(new DefaultHandler()); + _server.setHandler(wsHandler); + + _server.start(); + } + + @AfterClass + public static void stopServer() throws Exception + { + _server.stop(); + _server.join(); + } + + @Test + public void testLoad() throws Exception + { + int count = 50; + int iterations = 100; + + ExecutorService threadPool = Executors.newCachedThreadPool(); + try + { + CountDownLatch latch = new CountDownLatch(count * iterations); + WebSocketClient[] clients = new WebSocketClient[count]; + for (int i = 0; i < clients.length; ++i) + { + clients[i] = new WebSocketClient("localhost", _connector.getLocalPort(), 1000, latch, iterations); + clients[i].open(); + } + + //long start = System.nanoTime(); + for (WebSocketClient client : clients) + threadPool.execute(client); + + int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); + long maxTimePerIteration = 5; + assertTrue(latch.await(iterations * (count / parallelism + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS)); + //long end = System.nanoTime(); + // System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms"); + + for (WebSocketClient client : clients) + client.close(); + } + finally + { + threadPool.shutdown(); + assertTrue(threadPool.awaitTermination(2, TimeUnit.SECONDS)); + } + } + + private static class EchoWebSocket implements WebSocket.OnTextMessage + { + private volatile Connection outbound; + + public void onOpen(Connection outbound) + { + this.outbound = outbound; + } + + public void onMessage(String data) + { + try + { + // System.err.println(">> "+data); + outbound.sendMessage(data); + } + catch (IOException x) + { + outbound.disconnect(); + } + } + + public void onClose(int closeCode, String message) + { + } + } + + private class WebSocketClient implements Runnable + { + private final Socket socket; + private final BufferedWriter output; + private final BufferedReader input; + private final int iterations; + private final CountDownLatch latch; + private final SocketEndPoint _endp; + private final WebSocketGeneratorRFC6455 _generator; + private final WebSocketParserD13 _parser; + private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler() + { + public void onFrame(byte flags, byte opcode, Buffer buffer) + { + _response=buffer; + } + + public void close(int code,String message) + { + } + }; + private volatile Buffer _response; + + public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException + { + this.latch = latch; + socket = new Socket(host, port); + socket.setSoTimeout(readTimeout); + output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1")); + input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1")); + this.iterations = iterations; + + _endp=new SocketEndPoint(socket); + _generator = new WebSocketGeneratorRFC6455(new WebSocketBuffers(32*1024),_endp,new FixedMaskGen()); + _parser = new WebSocketParserD13(new WebSocketBuffers(32*1024),_endp,_handler,false); + + } + + private void open() throws IOException + { + output.write("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: 7\r\n"+ + "\r\n"); + output.flush(); + + String responseLine = input.readLine(); + assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols")); + // Read until we find an empty line, which signals the end of the http response + String line; + while ((line = input.readLine()) != null) + if (line.length() == 0) + break; + } + + public void run() + { + try + { + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + for (int i = 0; i < iterations; ++i) + { + byte[] data = message.getBytes(StringUtil.__UTF8); + _generator.addFrame((byte)0x8,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length); + _generator.flush(); + + //System.err.println("-> "+message); + + _response=null; + while(_response==null) + _parser.parseNext(); + //System.err.println("<- "+_response); + Assert.assertEquals(message,_response.toString()); + latch.countDown(); + } + } + catch (IOException x) + { + throw new RuntimeException(x); + } + } + + + public void close() throws IOException + { + socket.close(); + } + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java index 99781856907..fd2c02cbf2e 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java @@ -1,8 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -12,60 +25,80 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; +import java.net.SocketException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import javax.servlet.http.HttpServletRequest; +import junit.framework.Assert; + import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketMessageD00Test { - private static Server _server; - private static Connector _connector; - private static TestWebSocket _serverWebSocket; + private static Server __server; + private static Connector __connector; + private static TestWebSocket __serverWebSocket; + private static CountDownLatch __latch; + private static AtomicInteger __textCount = new AtomicInteger(0); + @BeforeClass public static void startServer() throws Exception { - _server = new Server(); - _connector = new SelectChannelConnector(); - _server.addConnector(_connector); + __server = new Server(); + __connector = new SelectChannelConnector(); + __server.addConnector(__connector); WebSocketHandler wsHandler = new WebSocketHandler() { public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { - _serverWebSocket = new TestWebSocket(); - _serverWebSocket.onConnect=("onConnect".equals(protocol)); - return _serverWebSocket; + __serverWebSocket = new TestWebSocket(); + __serverWebSocket._onConnect=("onConnect".equals(protocol)); + __serverWebSocket._echo=("echo".equals(protocol)); + __serverWebSocket._latch=("latch".equals(protocol)); + if (__serverWebSocket._latch) + __latch=new CountDownLatch(1); + return __serverWebSocket; } }; wsHandler.setHandler(new DefaultHandler()); - _server.setHandler(wsHandler); - _server.start(); + __server.setHandler(wsHandler); + __server.start(); } @AfterClass public static void stopServer() throws Exception { - _server.stop(); - _server.join(); + __server.stop(); + __server.join(); } + @Before + public void reset() + { + __textCount.set(0); + } + @Test public void testServerSendBigStringMessage() throws Exception { - Socket socket = new Socket("localhost", _connector.getLocalPort()); + Socket socket = new Socket("localhost", __connector.getLocalPort()); OutputStream output = socket.getOutputStream(); output.write( ("GET /test HTTP/1.1\r\n" + @@ -91,8 +124,8 @@ public class WebSocketMessageD00Test if (line.length() == 0) break; - assertTrue(_serverWebSocket.awaitConnected(1000)); - assertNotNull(_serverWebSocket.outbound); + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.outbound); // read the hixie bytes char[] hixie=new char[16]; // should be bytes, but we know this example is all ascii @@ -114,7 +147,7 @@ public class WebSocketMessageD00Test String text = "0123456789ABCDEF"; for (int i = 0; i < 64 * 1024 / text.length(); ++i) message.append(text); - _serverWebSocket.outbound.sendMessage(message.toString()); + __serverWebSocket.outbound.sendMessage(message.toString()); // Read until we get 0xFF ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -136,7 +169,7 @@ public class WebSocketMessageD00Test @Test public void testServerSendOnConnect() throws Exception { - Socket socket = new Socket("localhost", _connector.getLocalPort()); + Socket socket = new Socket("localhost", __connector.getLocalPort()); OutputStream output = socket.getOutputStream(); output.write( ("GET /test HTTP/1.1\r\n" + @@ -189,8 +222,8 @@ public class WebSocketMessageD00Test } - assertTrue(_serverWebSocket.awaitConnected(1000)); - assertNotNull(_serverWebSocket.outbound); + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.outbound); looking_for="8jKS'y:G*Co,Wxa-"; while(true) @@ -221,17 +254,498 @@ public class WebSocketMessageD00Test assertEquals(0xff,input.read()); } + - private static class TestWebSocket implements WebSocket + @Test + public void testServerEcho() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(1000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: echo\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + + output.write(0x00); + byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i>> "+i); + output.flush(); + + long now=System.currentTimeMillis(); + long duration=now-start; + start=now; + if (max2000); // was blocked + } + + @Test + public void testBlockedProducer() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(60000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: latch\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + + final int count = 100000; + + __serverWebSocket.connection.setMaxIdleTime(60000); + __latch.countDown(); + + // wait 2s and then consume messages + final AtomicLong totalB=new AtomicLong(); + new Thread() + { + @Override + public void run() + { + try + { + Thread.sleep(2000); + + byte[] recv = new byte[32*1024]; + + int len=0; + while (len>=0) + { + totalB.addAndGet(len); + len=socket.getInputStream().read(recv,0,recv.length); + Thread.sleep(10); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + + + // Send enough messages to fill receive buffer + long max=0; + long start=System.currentTimeMillis(); + String mesg="How Now Brown Cow"; + for (int i=0;i1000); // was blocked + } + + + + @Test + public void testIdle() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(10000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.connection.setMaxIdleTime(250); + assertEquals(0x00,input.read()); + lookFor("sent on connect",input); + assertEquals(0xff,input.read()); + + assertEquals(-1,input.read()); + socket.close(); + + assertTrue(__serverWebSocket.awaitDisconnected(100)); + } + + @Test + public void testIdleBadClient() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(10000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.connection.setMaxIdleTime(250); + assertEquals(0x00,input.read()); + lookFor("sent on connect",input); + assertEquals(0xff,input.read()); + + assertEquals(-1,input.read()); + + assertTrue(__serverWebSocket.disconnected.getCount()>0); + assertTrue(__serverWebSocket.awaitDisconnected(1000)); + } + + @Test + public void testTCPClose() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(10000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.connection.setMaxIdleTime(250); + assertEquals(0x00,input.read()); + lookFor("sent on connect",input); + assertEquals(0xff,input.read()); + + + + socket.close(); + + assertTrue(__serverWebSocket.awaitDisconnected(500)); + + try + { + __serverWebSocket.connection.sendMessage("Don't send"); + assertTrue(false); + } + catch(IOException e) + { + assertTrue(true); + } + } + + @Test + public void testTCPHalfClose() throws Exception + { + // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true); + + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + socket.setSoTimeout(10000); + OutputStream output = socket.getOutputStream(); + InputStream input = socket.getInputStream(); + output.write( + ("GET /test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + + "\r\n"+ + "^n:ds[4U").getBytes("ISO-8859-1")); + output.flush(); + + lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input); + skipTo("\r\n\r\n",input); + lookFor("8jKS'y:G*Co,Wxa-",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.connection.setMaxIdleTime(250); + assertEquals(0x00,input.read()); + lookFor("sent on connect",input); + assertEquals(0xff,input.read()); + + + socket.shutdownOutput(); + + assertTrue(__serverWebSocket.awaitDisconnected(500)); + + assertEquals(-1,input.read()); + + // look for broken pipe + try + { + for (int i=0;i<1000;i++) + output.write(0); + Assert.fail(); + } + catch(SocketException e) + { + // expected + } + } + + + + + + + + + + + + + + private void lookFor(String string,InputStream in) + throws IOException + { + String orig=string; + Utf8StringBuilder scanned=new Utf8StringBuilder(); + try + { + while(true) + { + int b = in.read(); + if (b<0) + throw new EOFException(); + scanned.append((byte)b); + assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b); + if (string.length()==1) + break; + string=string.substring(1); + } + } + catch(IOException e) + { + System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'"); + throw e; + } + } + + private void skipTo(String string,InputStream in) + throws IOException + { + int state=0; + + while(true) + { + int b = in.read(); + if (b<0) + throw new EOFException(); + + if (b==string.charAt(state)) + { + state++; + if (state==string.length()) + break; + } + else + state=0; + } + } + + + + private static class TestWebSocket implements WebSocket.OnFrame, WebSocket, WebSocket.OnTextMessage + { + protected boolean _latch; + boolean _echo=true; + boolean _onConnect=false; + private volatile Connection outbound; + private final CountDownLatch connected = new CountDownLatch(1); + private final CountDownLatch disconnected = new CountDownLatch(1); + private volatile FrameConnection connection; + + public FrameConnection getConnection() + { + return connection; + } + + public void onHandshake(FrameConnection connection) + { + this.connection = connection; + } + public void onOpen(Connection outbound) { this.outbound = outbound; - if (onConnect) + if (_onConnect) { try { @@ -242,16 +756,55 @@ public class WebSocketMessageD00Test e.printStackTrace(); } } - latch.countDown(); + connected.countDown(); } private boolean awaitConnected(long time) throws InterruptedException { - return latch.await(time, TimeUnit.MILLISECONDS); + return connected.await(time, TimeUnit.MILLISECONDS); + } + + private boolean awaitDisconnected(long time) throws InterruptedException + { + return disconnected.await(time, TimeUnit.MILLISECONDS); } public void onClose(int code,String message) { + disconnected.countDown(); + } + + public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) + { + return true; + } + + public void onMessage(String data) + { + __textCount.incrementAndGet(); + if (_latch) + { + try + { + __latch.await(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + if (_echo) + { + try + { + outbound.sendMessage(data); + } + catch (IOException e) + { + e.printStackTrace(); + } + } } } } diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java index 1521f1a608b..5952f8f4df7 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java @@ -1,8 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.io.EOFException; import java.io.IOException; @@ -27,9 +40,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketMessageD06Test { private static Server _server; @@ -53,8 +63,8 @@ public class WebSocketMessageD06Test return _serverWebSocket; } }; - wsHandler.setBufferSize(8192); - wsHandler.setMaxIdleTime(1000); + wsHandler.getWebSocketFactory().setBufferSize(8192); + wsHandler.getWebSocketFactory().setMaxIdleTime(1000); wsHandler.setHandler(new DefaultHandler()); _server.setHandler(wsHandler); _server.start(); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java similarity index 92% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java index b687c8b2b41..be553c2441b 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java @@ -1,8 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.io.EOFException; import java.io.IOException; @@ -32,10 +45,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ -public class WebSocketMessageD12Test +public class WebSocketMessageD08Test { private static Server __server; private static Connector __connector; @@ -64,8 +74,8 @@ public class WebSocketMessageD12Test return __serverWebSocket; } }; - wsHandler.setBufferSize(8192); - wsHandler.setMaxIdleTime(1000); + wsHandler.getWebSocketFactory().setBufferSize(8192); + wsHandler.getWebSocketFactory().setMaxIdleTime(1000); wsHandler.setHandler(new DefaultHandler()); __server.setHandler(wsHandler); __server.start(); @@ -82,7 +92,7 @@ public class WebSocketMessageD12Test @Test public void testHash() { - assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD12.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); + assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD08.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); } @Test @@ -98,7 +108,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: chat, superchat\r\n"+ - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -123,7 +133,7 @@ public class WebSocketMessageD12Test String data=message.toString(); __serverWebSocket.connection.sendMessage(data); - assertEquals(WebSocketConnectionD12.OP_TEXT,input.read()); + assertEquals(WebSocketConnectionD08.OP_TEXT,input.read()); assertEquals(0x7e,input.read()); assertEquals(0x1f,input.read()); assertEquals(0xf6,input.read()); @@ -146,7 +156,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -181,7 +191,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: identity;param=0\r\n"+ "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+ "\r\n").getBytes("ISO-8859-1")); @@ -224,7 +234,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -278,7 +288,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+ "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+ "\r\n").getBytes("ISO-8859-1")); @@ -329,7 +339,7 @@ public class WebSocketMessageD12Test output.write(buf,0,l+3); output.flush(); - assertEquals(0x40+WebSocketConnectionD12.OP_TEXT,input.read()); + assertEquals(0x40+WebSocketConnectionD08.OP_TEXT,input.read()); assertEquals(0x20+3,input.read()); assertEquals(0x7e,input.read()); assertEquals(0x02,input.read()); @@ -376,7 +386,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); output.write(0x84); @@ -415,7 +425,7 @@ public class WebSocketMessageD12Test byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1); byte[] mesg=new byte[bytes.length+6]; - mesg[0]=(byte)(0x80+WebSocketConnectionD12.OP_TEXT); + mesg[0]=(byte)(0x80+WebSocketConnectionD08.OP_TEXT); mesg[1]=(byte)(0x80+bytes.length); mesg[2]=(byte)0xff; mesg[3]=(byte)0xff; @@ -434,7 +444,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: latch\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -461,6 +471,7 @@ public class WebSocketMessageD12Test // unblock the latch in 4s new Thread() { + @Override public void run() { try @@ -521,7 +532,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: latch\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -544,6 +555,7 @@ public class WebSocketMessageD12Test final AtomicLong totalB=new AtomicLong(); new Thread() { + @Override public void run() { try @@ -569,29 +581,23 @@ public class WebSocketMessageD12Test // Send enough messages to fill receive buffer - long max=0; long start=System.currentTimeMillis(); String mesg="How Now Brown Cow"; for (int i=0;i1000); // was blocked + // if (duration<1500) + System.err.println("max="+duration); + assertTrue(duration>1500); // was blocked } @Test @@ -608,7 +614,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); output.write(0x89); @@ -647,7 +653,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -663,7 +669,7 @@ public class WebSocketMessageD12Test assertNotNull(__serverWebSocket.connection); __serverWebSocket.getConnection().setMaxTextMessageSize(10*1024); - __serverWebSocket.getConnection().setFakeFragments(true); + __serverWebSocket.getConnection().setAllowFrameFragmentation(true); output.write(0x81); output.write(0x80|0x7E); @@ -679,10 +685,10 @@ public class WebSocketMessageD12Test output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(33,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 10240 chars",input); } @@ -699,7 +705,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -737,10 +743,10 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(30,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 15 chars",input); } @@ -758,7 +764,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -788,10 +794,10 @@ public class WebSocketMessageD12Test - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(30,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 15 chars",input); } @@ -808,7 +814,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: aggregate\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -824,7 +830,7 @@ public class WebSocketMessageD12Test assertNotNull(__serverWebSocket.connection); __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024); - output.write(WebSocketConnectionD12.OP_BINARY); + output.write(WebSocketConnectionD08.OP_BINARY); output.write(0x8a); output.write(0xff); output.write(0xff); @@ -845,7 +851,7 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80+WebSocketConnectionD12.OP_BINARY,input.read()); + assertEquals(0x80+WebSocketConnectionD08.OP_BINARY,input.read()); assertEquals(20,input.read()); lookFor("01234567890123456789",input); } @@ -863,7 +869,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -902,10 +908,10 @@ public class WebSocketMessageD12Test output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(19,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Message size > 15",input); } @@ -923,7 +929,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -951,10 +957,10 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(19,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Message size > 15",input); } @@ -971,7 +977,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -1033,7 +1039,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -1077,14 +1083,14 @@ public class WebSocketMessageD12Test final AtomicReference received = new AtomicReference(); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - WebSocketGeneratorD12 gen = new WebSocketGeneratorD12(new WebSocketBuffers(8096),endp,null); + WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,null); byte[] data = message.getBytes(StringUtil.__UTF8); gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length); endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - WebSocketParserD12 parser = new WebSocketParserD12(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() { public void onFrame(byte flags, byte opcode, Buffer buffer) { @@ -1111,13 +1117,13 @@ public class WebSocketMessageD12Test MaskGen maskGen = new RandomMaskGen(); - WebSocketGeneratorD12 gen = new WebSocketGeneratorD12(new WebSocketBuffers(8096),endp,maskGen); + WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,maskGen); byte[] data = message.getBytes(StringUtil.__UTF8); gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length); endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - WebSocketParserD12 parser = new WebSocketParserD12(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() { public void onFrame(byte flags, byte opcode, Buffer buffer) { @@ -1241,9 +1247,9 @@ public class WebSocketMessageD12Test { switch(opcode) { - case WebSocketConnectionD12.OP_CLOSE: - case WebSocketConnectionD12.OP_PING: - case WebSocketConnectionD12.OP_PONG: + case WebSocketConnectionD08.OP_CLOSE: + case WebSocketConnectionD08.OP_PING: + case WebSocketConnectionD08.OP_PONG: break; default: diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java new file mode 100644 index 00000000000..6c017e93175 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java @@ -0,0 +1,1660 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import static org.junit.Assert.*; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import javax.servlet.http.HttpServletRequest; + +import junit.framework.Assert; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class WebSocketMessageRFC6455Test +{ + private static Server __server; + private static Connector __connector; + private static TestWebSocket __serverWebSocket; + private static CountDownLatch __latch; + private static AtomicInteger __textCount = new AtomicInteger(0); + + @BeforeClass + public static void startServer() throws Exception + { + __server = new Server(); + __connector = new SelectChannelConnector(); + __server.addConnector(__connector); + WebSocketHandler wsHandler = new WebSocketHandler() + { + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + __textCount.set(0); + __serverWebSocket = new TestWebSocket(); + __serverWebSocket._onConnect=("onConnect".equals(protocol)); + __serverWebSocket._echo=("echo".equals(protocol)); + __serverWebSocket._aggregate=("aggregate".equals(protocol)); + __serverWebSocket._latch=("latch".equals(protocol)); + if (__serverWebSocket._latch) + __latch=new CountDownLatch(1); + return __serverWebSocket; + } + }; + wsHandler.getWebSocketFactory().setBufferSize(8192); + wsHandler.getWebSocketFactory().setMaxIdleTime(1000); + wsHandler.setHandler(new DefaultHandler()); + __server.setHandler(wsHandler); + __server.start(); + } + + @AfterClass + public static void stopServer() throws Exception + { + __server.stop(); + __server.join(); + } + + + @Test + public void testHash() + { + assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionRFC6455.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); + } + + @Test + public void testServerSendBigStringMessage() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: chat, superchat\r\n"+ + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + // Server sends a big message + StringBuilder message = new StringBuilder(); + String text = "0123456789ABCDEF"; + for (int i = 0; i < (0x2000) / text.length(); i++) + message.append(text); + String data=message.toString(); + __serverWebSocket.connection.sendMessage(data); + + assertEquals(WebSocketConnectionRFC6455.OP_TEXT,input.read()); + assertEquals(0x7e,input.read()); + assertEquals(0x1f,input.read()); + assertEquals(0xf6,input.read()); + lookFor(data.substring(0,0x1ff6),input); + assertEquals(0x80,input.read()); + assertEquals(0x0A,input.read()); + lookFor(data.substring(0x1ff6),input); + } + + @Test + public void testServerSendOnConnect() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + } + + @Test + public void testIdentityExtension() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "Sec-WebSocket-Extensions: identity;param=0\r\n"+ + "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("identity;param=0",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("identity;param=1",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("identity;",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + } + + + @Test + public void testFragmentExtension() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("fragment;",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x01,input.read()); + assertEquals(0x04,input.read()); + lookFor("sent",input); + assertEquals(0x00,input.read()); + assertEquals(0x04,input.read()); + lookFor(" on ",input); + assertEquals(0x00,input.read()); + assertEquals(0x04,input.read()); + lookFor("conn",input); + assertEquals(0x00,input.read()); + assertEquals(0x01,input.read()); + lookFor("e",input); + assertEquals(0x00,input.read()); + assertEquals(0x01,input.read()); + lookFor("c",input); + assertEquals(0x00,input.read()); + assertEquals(0x00,input.read()); + assertEquals(0x80,input.read()); + assertEquals(0x01,input.read()); + lookFor("t",input); + } + + @Test + public void testDeflateFrameExtension() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: echo\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+ + "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("x-deflate-frame;minLength=64",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("fragment;",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + + // Server sends a big message + String text = "0123456789ABCDEF "; + text=text+text+text+text; + text=text+text+text+text; + text=text+text+text+text+'X'; + byte[] data=text.getBytes("utf-8"); + Deflater deflater = new Deflater(); + deflater.setInput(data); + deflater.finish(); + byte[] buf=new byte[data.length]; + + buf[0]=(byte)((byte)0x7e); + buf[1]=(byte)(data.length>>8); + buf[2]=(byte)(data.length&0xff); + + int l=deflater.deflate(buf,3,buf.length-3); + + assertTrue(deflater.finished()); + + output.write(0xC1); + output.write((byte)(0x80|(0xff&(l+3)))); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(buf,0,l+3); + output.flush(); + + assertEquals(0x40+WebSocketConnectionRFC6455.OP_TEXT,input.read()); + assertEquals(0x20+3,input.read()); + assertEquals(0x7e,input.read()); + assertEquals(0x02,input.read()); + assertEquals(0x20,input.read()); + + byte[] raw = new byte[32]; + assertEquals(32,input.read(raw)); + + Inflater inflater = new Inflater(); + inflater.setInput(raw); + + byte[] result = new byte[544]; + assertEquals(544,inflater.inflate(result)); + assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result)); + + + assertEquals((byte)0xC0,(byte)input.read()); + assertEquals(0x21+3,input.read()); + assertEquals(0x7e,input.read()); + assertEquals(0x02,input.read()); + assertEquals(0x21,input.read()); + + assertEquals(32,input.read(raw)); + + inflater.reset(); + inflater.setInput(raw); + result = new byte[545]; + assertEquals(545,inflater.inflate(result)); + assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result)); + + + } + + @Test + public void testServerEcho() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: echo\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + output.write(0x84); + output.write(0x8f); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i>> "+i); + output.flush(); + + long now=System.currentTimeMillis(); + long duration=now-start; + start=now; + if (max2000); // was blocked + } + + @Test + public void testBlockedProducer() throws Exception + { + final Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + + final int count = 100000; + + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: latch\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(60000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.connection.setMaxIdleTime(60000); + __latch.countDown(); + + // wait 2s and then consume messages + final AtomicLong totalB=new AtomicLong(); + new Thread() + { + @Override + public void run() + { + try + { + Thread.sleep(2000); + + byte[] recv = new byte[32*1024]; + + int len=0; + while (len>=0) + { + totalB.addAndGet(len); + len=socket.getInputStream().read(recv,0,recv.length); + Thread.sleep(10); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + + + // Send enough messages to fill receive buffer + long max=0; + long start=System.currentTimeMillis(); + String mesg="How Now Brown Cow"; + for (int i=0;i1000); // was blocked + } + + @Test + public void testServerPingPong() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + // Make sure the read times out if there are problems with the implementation + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: echo\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + output.write(0x89); + output.write(0x80); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.flush(); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + socket.setSoTimeout(1000); + assertEquals(0x8A,input.read()); + assertEquals(0x00,input.read()); + } + + @Test + public void testMaxTextSizeFalseFrag() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: other\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(1000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(10*1024); + __serverWebSocket.getConnection().setAllowFrameFragmentation(true); + + output.write(0x81); + output.write(0x80|0x7E); + output.write((byte)((16*1024)>>8)); + output.write((byte)((16*1024)&0xff)); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + + for (int i=0;i<(16*1024);i++) + output.write('X'); + output.flush(); + + + assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read()); + assertEquals(33,input.read()); + int code=(0xff&input.read())*0x100+(0xff&input.read()); + assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Text message size > 10240 chars",input); + } + + @Test + public void testMaxTextSize() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: other\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(1000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(15); + + output.write(0x01); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i 15 chars",input); + } + + + @Test + public void testMaxTextSize2() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: other\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(15); + + output.write(0x01); + output.write(0x94); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i 15 chars",input); + } + + @Test + public void testBinaryAggregate() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: aggregate\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(1000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024); + + output.write(WebSocketConnectionRFC6455.OP_BINARY); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i 15",input); + } + + + + @Test + public void testCloseIn() throws Exception + { + int[][] tests = + { + {-1,0,-1}, + {-1,0,-1}, + {1000,2,1000}, + {1000,2+4,1000}, + {1005,2+23,1002}, + {1005,2+23,1002}, + {1006,2+23,1002}, + {1006,2+23,1002}, + {4000,2,4000}, + {4000,2+4,4000}, + {9000,2+23,1002}, + {9000,2+23,1002} + }; + + String[] mesg = + { + "", + "", + "", + "mesg", + "", + "mesg", + "", + "mesg", + "", + "mesg", + "", + "mesg" + }; + + String[] resp = + { + "", + "", + "", + "mesg", + "Invalid close code 1005", + "Invalid close code 1005", + "Invalid close code 1006", + "Invalid close code 1006", + "", + "mesg", + "Invalid close code 9000", + "Invalid close code 9000" + }; + + for (int t=0;t0) + { + output.write(code/0x100); + output.write(code%0x100); + output.write(m.getBytes()); + } + output.flush(); + + __serverWebSocket.awaitDisconnected(1000); + + byte[] buf = new byte[128]; + int len = input.read(buf); + + assertEquals(tst,2+tests[t][1],len); + assertEquals(tst,(byte)0x88,buf[0]); + + if (len>=4) + { + code=(0xff&buf[2])*0x100+(0xff&buf[3]); + assertEquals(tst,tests[t][2],code); + + if (len>4) + { + m = new String(buf,4,len-4,"UTF-8"); + assertEquals(tst,resp[t],m); + } + } + else + assertEquals(tst,tests[t][2],-1); + + + len = input.read(buf); + assertEquals(tst,-1,len); + } + } + + + + @Test + public void testCloseOut() throws Exception + { + int[][] tests = + { + {-1,0,-1}, + {-1,0,-1}, + {0,2,1000}, + {0,2+4,1000}, + {1000,2,1000}, + {1000,2+4,1000}, + {1005,0,-1}, + {1005,0,-1}, + {1006,0,-1}, + {1006,0,-1}, + {9000,2,9000}, + {9000,2+4,9000} + }; + + String[] mesg = + { + null, + "Not Sent", + null, + "mesg", + null, + "mesg", + null, + "mesg", + null, + "mesg", + null, + "mesg" + }; + + for (int t=0;t=4) + { + int code=(0xff&buf[2])*0x100+(0xff&buf[3]); + assertEquals(tst,tests[t][2],code); + + if (len>4) + { + String m = new String(buf,4,len-4,"UTF-8"); + assertEquals(tst,mesg[t],m); + } + } + else + assertEquals(tst,tests[t][2],-1); + + try + { + output.write(0x88); + output.write(0x80); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.flush(); + } + catch(IOException e) + { + System.err.println("socket "+socket); + throw e; + } + + len = input.read(buf); + assertEquals(tst,-1,len); + } + } + + + @Test + public void testNotUTF8() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: chat\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + + output.write(0x81); + output.write(0x82); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0xc3); + output.write(0x28); + output.flush(); + + assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read()); + assertEquals(15,input.read()); + int code=(0xff&input.read())*0x100+(0xff&input.read()); + assertEquals(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,code); + lookFor("Invalid UTF-8",input); + } + + @Test + public void testMaxBinarySize2() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: other\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + + output.write(0x02); + output.write(0x94); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); + for (int i=0;i 15",input); + } + + @Test + public void testIdle() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(10000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + + assertEquals((byte)0x88,(byte)input.read()); + assertEquals(26,input.read()); + assertEquals(1000/0x100,input.read()); + assertEquals(1000%0x100,input.read()); + lookFor("Idle",input); + + // respond to close + output.write(0x88^0xff); + output.write(0x80^0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.flush(); + + + assertTrue(__serverWebSocket.awaitDisconnected(5000)); + try + { + __serverWebSocket.connection.sendMessage("Don't send"); + assertTrue(false); + } + catch(IOException e) + { + assertTrue(true); + } + } + + @Test + public void testTCPClose() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + socket.close(); + + assertTrue(__serverWebSocket.awaitDisconnected(500)); + + try + { + __serverWebSocket.connection.sendMessage("Don't send"); + assertTrue(false); + } + catch(IOException e) + { + assertTrue(true); + } + } + + @Test + public void testTCPHalfClose() throws Exception + { + Socket socket = new Socket("localhost", __connector.getLocalPort()); + OutputStream output = socket.getOutputStream(); + output.write( + ("GET /chat HTTP/1.1\r\n"+ + "Host: server.example.com\r\n"+ + "Upgrade: websocket\r\n"+ + "Connection: Upgrade\r\n"+ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ + "Sec-WebSocket-Origin: http://example.com\r\n"+ + "Sec-WebSocket-Protocol: onConnect\r\n" + + "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + // Make sure the read times out if there are problems with the implementation + socket.setSoTimeout(1000); + + InputStream input = socket.getInputStream(); + + lookFor("HTTP/1.1 101 Switching Protocols\r\n",input); + skipTo("Sec-WebSocket-Accept: ",input); + lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input); + skipTo("\r\n\r\n",input); + + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + + socket.shutdownOutput(); + + assertTrue(__serverWebSocket.awaitDisconnected(500)); + + assertEquals(0x88,input.read()); + assertEquals(0x00,input.read()); + assertEquals(-1,input.read()); + + // look for broken pipe + try + { + for (int i=0;i<1000;i++) + output.write(0); + Assert.fail(); + } + catch(SocketException e) + { + // expected + } + } + + + + @Test + public void testParserAndGenerator() throws Exception + { + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + final AtomicReference received = new AtomicReference(); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + + WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,null); + + byte[] data = message.getBytes(StringUtil.__UTF8); + gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length); + + endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); + + WebSocketParserD13 parser = new WebSocketParserD13(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + { + public void onFrame(byte flags, byte opcode, Buffer buffer) + { + received.set(buffer.toString()); + } + + public void close(int code,String message) + { + } + + },false); + + parser.parseNext(); + + assertEquals(message,received.get()); + } + + @Test + public void testParserAndGeneratorMasked() throws Exception + { + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + final AtomicReference received = new AtomicReference(); + ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); + + MaskGen maskGen = new RandomMaskGen(); + + WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,maskGen); + byte[] data = message.getBytes(StringUtil.__UTF8); + gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length); + + endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); + + WebSocketParserD13 parser = new WebSocketParserD13(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + { + public void onFrame(byte flags, byte opcode, Buffer buffer) + { + received.set(buffer.toString()); + } + + public void close(int code,String message) + { + } + },true); + + parser.parseNext(); + + assertEquals(message,received.get()); + } + + + private void lookFor(String string,InputStream in) + throws IOException + { + String orig=string; + Utf8StringBuilder scanned=new Utf8StringBuilder(); + try + { + while(true) + { + int b = in.read(); + if (b<0) + throw new EOFException(); + scanned.append((byte)b); + assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b); + if (string.length()==1) + break; + string=string.substring(1); + } + } + catch(IOException e) + { + System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'"); + throw e; + } + } + + private void skipTo(String string,InputStream in) + throws IOException + { + int state=0; + + while(true) + { + int b = in.read(); + if (b<0) + throw new EOFException(); + + if (b==string.charAt(state)) + { + state++; + if (state==string.length()) + break; + } + else + state=0; + } + } + + + private static class TestWebSocket implements WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage + { + protected boolean _latch; + boolean _onConnect=false; + boolean _echo=true; + boolean _aggregate=false; + private final CountDownLatch connected = new CountDownLatch(1); + private final CountDownLatch disconnected = new CountDownLatch(1); + private volatile FrameConnection connection; + + public FrameConnection getConnection() + { + return connection; + } + + public void onHandshake(FrameConnection connection) + { + this.connection = connection; + } + + public void onOpen(Connection connection) + { + if (_onConnect) + { + try + { + connection.sendMessage("sent on connect"); + } + catch(IOException e) + { + e.printStackTrace(); + } + } + connected.countDown(); + } + + private boolean awaitConnected(long time) throws InterruptedException + { + return connected.await(time, TimeUnit.MILLISECONDS); + } + + private boolean awaitDisconnected(long time) throws InterruptedException + { + return disconnected.await(time, TimeUnit.MILLISECONDS); + } + + public void onClose(int code,String message) + { + disconnected.countDown(); + } + + public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) + { + if (_echo) + { + switch(opcode) + { + case WebSocketConnectionRFC6455.OP_CLOSE: + case WebSocketConnectionRFC6455.OP_PING: + case WebSocketConnectionRFC6455.OP_PONG: + break; + + default: + try + { + connection.sendFrame(flags,opcode,data,offset,length); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + return false; + } + + public void onMessage(byte[] data, int offset, int length) + { + if (_aggregate) + { + try + { + connection.sendMessage(data,offset,length); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + public void onMessage(String data) + { + __textCount.incrementAndGet(); + if (_latch) + { + try + { + __latch.await(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + if (_aggregate) + { + try + { + connection.sendMessage(data); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java new file mode 100644 index 00000000000..80b262d6392 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class WebSocketOverSSLTest +{ + private Server _server; + private int _port; + private WebSocket.Connection _connection; + + private void startServer(final WebSocket webSocket) throws Exception + { + _server = new Server(); + SslSelectChannelConnector connector = new SslSelectChannelConnector(); + _server.addConnector(connector); + SslContextFactory cf = connector.getSslContextFactory(); + cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath()); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + _server.setHandler(new WebSocketHandler() + { + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + return webSocket; + } + }); + _server.start(); + _port = connector.getLocalPort(); + } + + private void startClient(final WebSocket webSocket) throws Exception + { + Assert.assertTrue(_server.isStarted()); + + WebSocketClientFactory factory = new WebSocketClientFactory(new QueuedThreadPool(), new ZeroMaskGen()); + SslContextFactory cf = factory.getSslContextFactory(); + cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath()); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + factory.start(); + WebSocketClient client = new WebSocketClient(factory); + _connection = client.open(new URI("wss://localhost:" + _port), webSocket).get(5, TimeUnit.SECONDS); + } + + @After + public void destroy() throws Exception + { + if (_connection != null) + _connection.close(); + if (_server != null) + { + _server.stop(); + _server.join(); + } + } + + @Test + public void testWebSocketOverSSL() throws Exception + { + final String message = "message"; + final CountDownLatch serverLatch = new CountDownLatch(1); + startServer(new WebSocket.OnTextMessage() + { + private Connection connection; + + public void onOpen(Connection connection) + { + this.connection = connection; + } + + public void onMessage(String data) + { + try + { + Assert.assertEquals(message, data); + connection.sendMessage(data); + serverLatch.countDown(); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + + public void onClose(int closeCode, String message) + { + } + }); + final CountDownLatch clientLatch = new CountDownLatch(1); + startClient(new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + } + + public void onMessage(String data) + { + Assert.assertEquals(message, data); + clientLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + } + }); + _connection.sendMessage(message); + + Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testManyMessages() throws Exception + { + startServer(new WebSocket.OnTextMessage() + { + private Connection connection; + + public void onOpen(Connection connection) + { + this.connection = connection; + } + + public void onMessage(String data) + { + try + { + connection.sendMessage(data); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + + public void onClose(int closeCode, String message) + { + } + }); + int count = 1000; + final CountDownLatch clientLatch = new CountDownLatch(count); + startClient(new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + } + + public void onMessage(String data) + { + clientLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + } + }); + + char[] chars = new char[256]; + Arrays.fill(chars, 'x'); + String message = new String(chars); + for (int i = 0; i < count; ++i) + _connection.sendMessage(message); + + Assert.assertTrue(clientLatch.await(20, TimeUnit.SECONDS)); + + // While messages may have all arrived, the SSL close alert + // may be in the way so give some time for it to be processed. + TimeUnit.SECONDS.sleep(1); + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java index bfdcf1bae35..7e38a9b71ff 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java @@ -1,8 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -16,9 +30,6 @@ import org.eclipse.jetty.util.StringUtil; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketParserD00Test { private ByteArrayBuffer _in; @@ -51,7 +62,7 @@ public class WebSocketParserD00Test int filled =_parser.parseNext(); - assertEquals(13,filled); + assertThat(filled,greaterThan(0)); assertEquals("Hello World",_handler._data.get(0)); assertTrue(_parser.isBufferEmpty()); assertTrue(_parser.getBuffer()==null); @@ -69,14 +80,14 @@ public class WebSocketParserD00Test int filled =_parser.parseNext(); - assertEquals(30,filled); + assertThat(filled,greaterThan(0)); assertEquals("Hello World",_handler._data.get(0)); assertFalse(_parser.isBufferEmpty()); assertFalse(_parser.getBuffer()==null); filled =_parser.parseNext(); - assertEquals(0,filled); + assertThat(filled,greaterThan(0)); assertEquals("Hell\uFF4f W\uFF4Frld",_handler._data.get(1)); assertTrue(_parser.isBufferEmpty()); assertTrue(_parser.getBuffer()==null); @@ -91,7 +102,7 @@ public class WebSocketParserD00Test int filled =_parser.parseNext(); - assertEquals(13,filled); + assertThat(filled,greaterThan(0)); assertEquals("Hello World",_handler._data.get(0)); assertTrue(_parser.isBufferEmpty()); assertTrue(_parser.getBuffer()==null); @@ -113,15 +124,14 @@ public class WebSocketParserD00Test _in.put((byte)(data.length&0x7f)); _in.put(data); - int filled =_parser.parseNext(); - assertEquals(13+3+data.length,filled); + assertThat(filled,greaterThan(0)); assertEquals("Hello World",_handler._data.get(0)); assertFalse(_parser.isBufferEmpty()); assertFalse(_parser.getBuffer()==null); filled =_parser.parseNext(); - assertEquals(0,filled); + assertThat(filled,greaterThan(0)); String got=_handler._data.get(1); assertEquals(data.length,got.length()); assertTrue(got.startsWith("012345678901234567890123")); @@ -130,13 +140,6 @@ public class WebSocketParserD00Test } - // TODO test: - // blocking, - // async - // full - // EOF - // errors - private class Handler implements WebSocketParser.FrameHandler { public List _data = new ArrayList(); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java index bcd513657a0..bb9c69049c2 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java @@ -1,8 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -17,9 +30,6 @@ import org.eclipse.jetty.util.Utf8StringBuilder; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ public class WebSocketParserD06Test { private MaskedByteArrayBuffer _in; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java similarity index 90% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java index 3685eefb0da..fa156eac4f8 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java @@ -1,8 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ package org.eclipse.jetty.websocket; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -17,14 +30,11 @@ import org.eclipse.jetty.util.Utf8StringBuilder; import org.junit.Before; import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ -public class WebSocketParserD12Test +public class WebSocketParserD08Test { private MaskedByteArrayBuffer _in; private Handler _handler; - private WebSocketParserD12 _parser; + private WebSocketParserD08 _parser; private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF}; private int _m; @@ -87,7 +97,7 @@ public class WebSocketParserD12Test ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); endPoint.setNonBlocking(true); _handler = new Handler(); - _parser=new WebSocketParserD12(buffers, endPoint,_handler,true); + _parser=new WebSocketParserD08(buffers, endPoint,_handler,true); _parser.setFakeFragments(false); _in = new MaskedByteArrayBuffer(); @@ -192,7 +202,7 @@ public class WebSocketParserD12Test { WebSocketBuffers buffers = new WebSocketBuffers(0x20000); ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - WebSocketParserD12 parser=new WebSocketParserD12(buffers, endPoint,_handler,false); + WebSocketParserD08 parser=new WebSocketParserD08(buffers, endPoint,_handler,false); ByteArrayBuffer in = new ByteArrayBuffer(0x20000); endPoint.setIn(in); @@ -269,7 +279,7 @@ public class WebSocketParserD12Test assertTrue(progress>0); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,_handler._code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,_handler._code); for (int i=0;i<2048;i++) _in.put((byte)'a'); progress =_parser.parseNext(); @@ -313,7 +323,7 @@ public class WebSocketParserD12Test assertTrue(progress>0); assertEquals(2,_handler._frames); - assertEquals(WebSocketConnectionD12.OP_CONTINUATION,_handler._opcode); + assertEquals(WebSocketConnectionD08.OP_CONTINUATION,_handler._opcode); assertEquals(1,_handler._data.size()); String mesg=_handler._data.remove(0); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java new file mode 100644 index 00000000000..34d0f1efdd1 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java @@ -0,0 +1,404 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.junit.Before; +import org.junit.Test; + +public class WebSocketParserRFC6455Test +{ + private ByteArrayEndPoint _endPoint; + private MaskedByteArrayBuffer _in; + private Handler _handler; + private WebSocketParserD13 _parser; + private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF}; + private int _m; + + class MaskedByteArrayBuffer extends ByteArrayBuffer + { + MaskedByteArrayBuffer() + { + super(4096); + } + + public void sendMask() + { + super.poke(putIndex(),_mask,0,4); + super.setPutIndex(putIndex()+4); + _m=0; + } + + @Override + public int put(Buffer src) + { + return put(src.asArray(),0,src.length()); + } + + public void putUnmasked(byte b) + { + super.put(b); + } + + @Override + public void put(byte b) + { + super.put((byte)(b^_mask[_m++%4])); + } + + @Override + public int put(byte[] b, int offset, int length) + { + byte[] mb = new byte[b.length]; + final int end=offset+length; + for (int i=offset;i0); + assertEquals(0xf,_handler._flags); + assertEquals(0xf,_handler._opcode); + assertTrue(_parser.isBufferEmpty()); + _parser.returnBuffer(); + assertTrue(_parser.getBuffer()==null); + } + + @Test + public void testShortText() throws Exception + { + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)(0x80|11)); + _in.sendMask(); + _in.put("Hello World".getBytes(StringUtil.__UTF8)); + // System.err.println("tosend="+TypeUtil.toHexString(_in.asArray())); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals("Hello World",_handler._data.get(0)); + assertEquals(0x8,_handler._flags); + assertEquals(0x1,_handler._opcode); + assertTrue(_parser.isBufferEmpty()); + _parser.returnBuffer(); + assertTrue(_parser.getBuffer()==null); + } + + @Test + public void testShortUtf8() throws Exception + { + String string = "Hell\uFF4f W\uFF4Frld"; + byte[] bytes = string.getBytes("UTF-8"); + + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)(0x80|bytes.length)); + _in.sendMask(); + _in.put(bytes); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(string,_handler._data.get(0)); + assertEquals(0x8,_handler._flags); + assertEquals(0x1,_handler._opcode); + _parser.returnBuffer(); + assertTrue(_parser.isBufferEmpty()); + assertTrue(_parser.getBuffer()==null); + } + + @Test + public void testMediumText() throws Exception + { + String string = "Hell\uFF4f Medium W\uFF4Frld "; + for (int i=0;i<4;i++) + string = string+string; + string += ". The end."; + + byte[] bytes = string.getBytes(StringUtil.__UTF8); + + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)(0x80|0x7E)); + _in.putUnmasked((byte)(bytes.length>>8)); + _in.putUnmasked((byte)(bytes.length&0xff)); + _in.sendMask(); + _in.put(bytes); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(string,_handler._data.get(0)); + assertEquals(0x8,_handler._flags); + assertEquals(0x1,_handler._opcode); + _parser.returnBuffer(); + assertTrue(_parser.isBufferEmpty()); + assertTrue(_parser.getBuffer()==null); + } + + @Test + public void testLongText() throws Exception + { + WebSocketBuffers buffers = new WebSocketBuffers(0x20000); + ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); + WebSocketParserD13 parser=new WebSocketParserD13(buffers, endPoint,_handler,false); + ByteArrayBuffer in = new ByteArrayBuffer(0x20000); + endPoint.setIn(in); + + String string = "Hell\uFF4f Big W\uFF4Frld "; + for (int i=0;i<12;i++) + string = string+string; + string += ". The end."; + + byte[] bytes = string.getBytes("UTF-8"); + + _in.sendMask(); + in.put((byte)0x84); + in.put((byte)0x7F); + in.put((byte)0x00); + in.put((byte)0x00); + in.put((byte)0x00); + in.put((byte)0x00); + in.put((byte)0x00); + in.put((byte)(bytes.length>>16)); + in.put((byte)((bytes.length>>8)&0xff)); + in.put((byte)(bytes.length&0xff)); + in.put(bytes); + + int progress =parser.parseNext(); + parser.returnBuffer(); + + assertTrue(progress>0); + assertEquals(string,_handler._data.get(0)); + assertTrue(parser.isBufferEmpty()); + assertTrue(parser.getBuffer()==null); + } + + @Test + public void testShortFragmentTest() throws Exception + { + _in.putUnmasked((byte)0x01); + _in.putUnmasked((byte)0x86); + _in.sendMask(); + _in.put("Hello ".getBytes(StringUtil.__UTF8)); + _in.putUnmasked((byte)0x80); + _in.putUnmasked((byte)0x85); + _in.sendMask(); + _in.put("World".getBytes(StringUtil.__UTF8)); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(0,_handler._data.size()); + assertFalse(_parser.isBufferEmpty()); + assertFalse(_parser.getBuffer()==null); + + progress =_parser.parseNext(); + _parser.returnBuffer(); + + assertTrue(progress>0); + assertEquals("Hello World",_handler._data.get(0)); + assertTrue(_parser.isBufferEmpty()); + assertTrue(_parser.getBuffer()==null); + } + + @Test + public void testFrameTooLarge() throws Exception + { + // Buffers are only 1024, so this frame is too large + _parser.setFakeFragments(false); + + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)(0x80|0x7E)); + _in.putUnmasked((byte)(2048>>8)); + _in.putUnmasked((byte)(2048&0xff)); + _in.sendMask(); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,_handler._code); + + + for (int i=0;i<2048;i++) + _in.put((byte)'a'); + progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(0,_handler._data.size()); + assertEquals(0,_handler._utf8.length()); + + _handler._code=0; + + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)0xFE); + _in.putUnmasked((byte)(1024>>8)); + _in.putUnmasked((byte)(1024&0xff)); + _in.sendMask(); + for (int i=0;i<1024;i++) + _in.put((byte)'a'); + + progress =_parser.parseNext(); + assertTrue(progress>0); + assertEquals(0,_handler._data.size()); + } + + @Test + public void testFakeFragement() throws Exception + { + // Buffers are only 1024, so this frame will be fake fragmented + _parser.setFakeFragments(true); + + _in.putUnmasked((byte)0x81); + _in.putUnmasked((byte)(0x80|0x7E)); + _in.putUnmasked((byte)(2048>>8)); + _in.putUnmasked((byte)(2048&0xff)); + _in.sendMask(); + for (int i=0;i<2048;i++) + _in.put((byte)('a'+i%26)); + + int progress =_parser.parseNext(); + assertTrue(progress>0); + + assertEquals(2,_handler._frames); + assertEquals(WebSocketConnectionRFC6455.OP_CONTINUATION,_handler._opcode); + assertEquals(1,_handler._data.size()); + String mesg=_handler._data.remove(0); + + assertEquals(2048,mesg.length()); + + for (int i=0;i<2048;i++) + assertEquals(('a'+i%26),mesg.charAt(i)); + } + + @Test + public void testClose() throws Exception + { + String string = "Game Over"; + byte[] bytes = string.getBytes("UTF-8"); + + _in.putUnmasked((byte)(0x80|0x08)); + _in.putUnmasked((byte)(0x80|(2+bytes.length))); + _in.sendMask(); + _in.put((byte)(1000/0x100)); + _in.put((byte)(1000%0x100)); + _in.put(bytes); + + int progress =_parser.parseNext(); + + assertTrue(progress>0); + assertEquals(string,_handler._data.get(0).substring(2)); + assertEquals(0x8,_handler._flags); + assertEquals(0x8,_handler._opcode); + _parser.returnBuffer(); + assertTrue(_parser.isBufferEmpty()); + assertTrue(_parser.getBuffer()==null); + + _in.clear(); + _in.put(bytes); + _endPoint.setIn(_in); + progress =_parser.parseNext(); + assertTrue(progress>0); + + _endPoint.shutdownInput(); + + progress =_parser.parseNext(); + assertEquals(-1,progress); + + } + + + private class Handler implements WebSocketParser.FrameHandler + { + Utf8StringBuilder _utf8 = new Utf8StringBuilder(); + public List _data = new ArrayList(); + private byte _flags; + private byte _opcode; + int _code; + int _frames; + + public void onFrame(byte flags, byte opcode, Buffer buffer) + { + _frames++; + _flags=flags; + _opcode=opcode; + if ((flags&0x8)==0) + _utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); + else if (_utf8.length()==0) + _data.add(buffer.toString("utf-8")); + else + { + _utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); + _data.add(_utf8.toString()); + _utf8.reset(); + } + } + + public void close(int code,String message) + { + _code=code; + } + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java new file mode 100644 index 00000000000..39f218085d9 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ + +package org.eclipse.jetty.websocket; + +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class WebSocketRedeployTest +{ + private Server server; + private ServletContextHandler context; + private String uri; + private WebSocketClientFactory wsFactory; + + public void init(final WebSocket webSocket) throws Exception + { + server = new Server(); + SelectChannelConnector connector = new SelectChannelConnector(); +// connector.setPort(8080); + server.addConnector(connector); + + HandlerCollection handlers = new HandlerCollection(); + server.setHandler(handlers); + + String contextPath = "/test_context"; + context = new ServletContextHandler(handlers, contextPath, ServletContextHandler.SESSIONS); + + WebSocketServlet servlet = new WebSocketServlet() + { + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + return webSocket; + } + }; + String servletPath = "/websocket"; + context.addServlet(new ServletHolder(servlet), servletPath); + + server.start(); + + uri = "ws://localhost:" + connector.getLocalPort() + contextPath + servletPath; + + wsFactory = new WebSocketClientFactory(); + wsFactory.start(); + } + + @After + public void destroy() throws Exception + { + if (wsFactory != null) + wsFactory.stop(); + if (server != null) + { + server.stop(); + server.join(); + } + } + + @Test + public void testStoppingContextClosesConnections() throws Exception + { + final CountDownLatch openLatch = new CountDownLatch(2); + final CountDownLatch closeLatch = new CountDownLatch(2); + init(new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + openLatch.countDown(); + } + + public void onMessage(String data) + { + } + + public void onClose(int closeCode, String message) + { + closeLatch.countDown(); + } + }); + + WebSocketClient client = wsFactory.newWebSocketClient(); + client.open(new URI(uri), new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + openLatch.countDown(); + } + + public void onMessage(String data) + { + } + + public void onClose(int closeCode, String message) + { + closeLatch.countDown(); + } + }, 5, TimeUnit.SECONDS); + + Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS)); + + context.stop(); + + Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testStoppingClientFactoryClosesConnections() throws Exception + { + final CountDownLatch openLatch = new CountDownLatch(2); + final CountDownLatch closeLatch = new CountDownLatch(2); + init(new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + openLatch.countDown(); + } + + public void onMessage(String data) + { + } + + public void onClose(int closeCode, String message) + { + closeLatch.countDown(); + } + }); + + WebSocketClient client = wsFactory.newWebSocketClient(); + client.open(new URI(uri), new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + openLatch.countDown(); + } + + public void onMessage(String data) + { + } + + public void onClose(int closeCode, String message) + { + closeLatch.countDown(); + } + }, 5, TimeUnit.SECONDS); + + Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS)); + + wsFactory.stop(); + + Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java new file mode 100644 index 00000000000..fcde6e3135e --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java @@ -0,0 +1,225 @@ +package org.eclipse.jetty.websocket; + +import static org.hamcrest.Matchers.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.helper.MessageSender; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test various RFC 6455 specified requirements placed on + * {@link WebSocketServlet} + *

    + * This test serves a different purpose than than the {@link WebSocketGeneratorRFC6455Test}, + * {@link WebSocketMessageRFC6455Test}, and {@link WebSocketParserRFC6455Test} tests. + */ +public class WebSocketServletRFCTest +{ + private static class RFCSocket implements WebSocket, WebSocket.OnTextMessage + { + private Connection conn; + + public void onOpen(Connection connection) + { + this.conn = connection; + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + } + + public void onMessage(String data) + { + // Test the RFC 6455 close code 1011 that should close + // trigger a WebSocket server terminated close. + if (data.equals("CRASH")) + { + throw new RuntimeException("Something bad happened"); + } + + // echo the message back. + try + { + conn.sendMessage(data); + } + catch (IOException e) + { + e.printStackTrace(System.err); + } + } + + } + + @SuppressWarnings("serial") + private static class RFCServlet extends WebSocketServlet + { + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + return new RFCSocket(); + } + } + + private static Server server; + private static URI serverUri; + + @BeforeClass + public static void startServer() throws Exception + { + // Configure Server + server = new Server(0); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + context.addServlet(new ServletHolder(new RFCServlet()),"/*"); + + // Start Server + server.start(); + + Connector conn = server.getConnectors()[0]; + String host = conn.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = conn.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d/",host,port)); + System.out.printf("Server URI: %s%n",serverUri); + } + + @AfterClass + public static void stopServer() + { + try + { + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(System.err); + } + } + + /** + * Test the requirement of responding with an http 400 when using a Sec-WebSocket-Version that is unsupported. + */ + @Test + public void testResponseOnInvalidVersion() throws Exception + { + // Using straight Socket to accomplish this as jetty's WebSocketClient + // doesn't allow the use of invalid versions. (obviously) + + Socket socket = new Socket(); + SocketAddress endpoint = new InetSocketAddress(serverUri.getHost(),serverUri.getPort()); + socket.connect(endpoint); + + StringBuilder req = new StringBuilder(); + req.append("GET / HTTP/1.1\r\n"); + req.append(String.format("Host: %s:%d\r\n",serverUri.getHost(),serverUri.getPort())); + req.append("Upgrade: WebSocket\r\n"); + req.append("Connection: Upgrade\r\n"); + req.append("Sec-WebSocket-Version: 29\r\n"); // bad version + req.append("\r\n"); + + OutputStream out = null; + InputStream in = null; + try + { + out = socket.getOutputStream(); + in = socket.getInputStream(); + + // Write request + out.write(req.toString().getBytes()); + out.flush(); + + // Read response + String respHeader = readResponseHeader(in); + // System.out.println("RESPONSE: " + respHeader); + + Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification")); + Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13, 8, 6, 0\r\n")); + } + finally + { + IO.close(in); + IO.close(out); + socket.close(); + } + } + + private String readResponseHeader(InputStream in) throws IOException + { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader reader = new BufferedReader(isr); + StringBuilder header = new StringBuilder(); + // Read the response header + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertThat(line,startsWith("HTTP/1.1 ")); + header.append(line).append("\r\n"); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + { + break; + } + header.append(line).append("\r\n"); + } + return header.toString(); + } + + /** + * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal + * server error) being produced by the extended WebSocketServlet. + */ + @Test + public void testResponseOnInternalError() throws Exception + { + WebSocketClientFactory clientFactory = new WebSocketClientFactory(); + clientFactory.start(); + + WebSocketClient wsc = clientFactory.newWebSocketClient(); + MessageSender sender = new MessageSender(); + wsc.open(serverUri,sender); + + try + { + sender.awaitConnect(); + + sender.sendMessage("CRASH"); + + // Give servlet 500 millisecond to process messages + TimeUnit.MILLISECONDS.sleep(500); + + Assert.assertThat("WebSocket should be closed",sender.isConnected(),is(false)); + Assert.assertThat("WebSocket close clode",sender.getCloseCode(),is(1011)); + } + finally + { + sender.close(); + } + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java new file mode 100644 index 00000000000..de969280a34 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket.helper; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.WebSocket; + +public class CaptureSocket implements WebSocket.OnTextMessage +{ + private final CountDownLatch latch = new CountDownLatch(1); + public List messages; + + public CaptureSocket() + { + messages = new ArrayList(); + } + + public boolean awaitConnected(long timeout) throws InterruptedException + { + return latch.await(timeout, TimeUnit.MILLISECONDS); + } + + public void onMessage(String data) + { + // System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length()); + messages.add(data); + } + + public void onOpen(Connection connection) + { + latch.countDown(); + } + + public void onClose(int closeCode, String message) + { + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java new file mode 100644 index 00000000000..65896aab982 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket.helper; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.WebSocket; + +public class MessageSender implements WebSocket +{ + private Connection conn; + private CountDownLatch connectLatch = new CountDownLatch(1); + private int closeCode = -1; + private String closeMessage = null; + + public void onOpen(Connection connection) + { + this.conn = connection; + connectLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + this.closeCode = closeCode; + this.closeMessage = message; + } + + public boolean isConnected() + { + if (this.conn == null) + { + return false; + } + return this.conn.isOpen(); + } + + public int getCloseCode() + { + return closeCode; + } + + public String getCloseMessage() + { + return closeMessage; + } + + public void sendMessage(String format, Object... args) throws IOException + { + this.conn.sendMessage(String.format(format,args)); + } + + public void awaitConnect() throws InterruptedException + { + connectLatch.await(1,TimeUnit.SECONDS); + } + + public void close() + { + if (this.conn == null) + { + return; + } + this.conn.close(); + } +} \ No newline at end of file diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java new file mode 100644 index 00000000000..85ab83c2ae4 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket.helper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.URI; + +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.TypeUtil; +import org.junit.Assert; + +import static org.hamcrest.Matchers.is; + +public class SafariD00 +{ + private URI uri; + private SocketAddress endpoint; + private Socket socket; + private OutputStream out; + private InputStream in; + + public SafariD00(URI uri) + { + this.uri = uri; + this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort()); + } + + /** + * Open the Socket to the destination endpoint and + * + * @return the open java Socket. + * @throws IOException + */ + public Socket connect() throws IOException + { + socket = new Socket(); + socket.connect(endpoint,1000); + + out = socket.getOutputStream(); + in = socket.getInputStream(); + + return socket; + } + + /** + * Issue an Http websocket (Draft-0) upgrade request using the Safari particulars. + * + * @throws UnsupportedEncodingException + */ + public void issueHandshake() throws IOException + { + StringBuilder req = new StringBuilder(); + req.append("GET ").append(uri.getPath()).append(" HTTP/1.1\r\n"); + req.append("Upgrade: WebSocket\r\n"); + req.append("Connection: Upgrade\r\n"); + req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n"); + req.append("Origin: http://www.google.com/\r\n"); + req.append("Sec-WebSocket-Key1: 15{ft :6@87 0 M 5 c901\r\n"); + req.append("Sec-WebSocket-Key2: 3? C;7~0 8 \" 3 2105 6 `_ {\r\n"); + req.append("\r\n"); + + // System.out.printf("--- Request ---%n%s",req); + + byte reqBytes[] = req.toString().getBytes("UTF-8"); + byte hixieBytes[] = TypeUtil.fromHexString("e739617916c9daf3"); + byte buf[] = new byte[reqBytes.length + hixieBytes.length]; + System.arraycopy(reqBytes,0,buf,0,reqBytes.length); + System.arraycopy(hixieBytes,0,buf,reqBytes.length,hixieBytes.length); + + // Send HTTP GET Request (with hixie bytes) + out.write(buf,0,buf.length); + out.flush(); + + // Read HTTP 101 Upgrade / Handshake Response + InputStreamReader reader = new InputStreamReader(in); + BufferedReader br = new BufferedReader(reader); + + socket.setSoTimeout(5000); + + boolean foundEnd = false; + String line; + while (!foundEnd) + { + line = br.readLine(); + // System.out.printf("RESP: %s%n",line); + if (line.length() == 0) + { + foundEnd = true; + } + } + + // Read expected handshake hixie bytes + byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809"); + byte hixieHandshake[] = new byte[hixieHandshakeExpected.length]; + + int readLen = in.read(hixieHandshake,0,hixieHandshake.length); + Assert.assertThat("Read hixie handshake bytes",readLen,is(hixieHandshake.length)); + } + + public void sendMessage(String... msgs) throws IOException + { + int len = 0; + for (String msg : msgs) + { + len += (msg.length() + 2); + } + + ByteArrayBuffer buf = new ByteArrayBuffer(len); + + for (String msg : msgs) + { + buf.put((byte)0x00); + buf.put(msg.getBytes("UTF-8")); + buf.put((byte)0xFF); + } + + out.write(buf.array()); + out.flush(); + } + + public void disconnect() throws IOException + { + socket.close(); + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java new file mode 100644 index 00000000000..b4731283823 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2011 Intalio, Inc. + * ====================================================================== + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + *******************************************************************************/ +package org.eclipse.jetty.websocket.helper; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.websocket.WebSocket; +import org.eclipse.jetty.websocket.WebSocketServlet; + +@SuppressWarnings("serial") +public class WebSocketCaptureServlet extends WebSocketServlet +{ + public List captures = new ArrayList();; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.sendError(404); + } + + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + CaptureSocket capture = new CaptureSocket(); + captures.add(capture); + return capture; + } +} \ No newline at end of file diff --git a/jetty-websocket/src/test/resources/keystore b/jetty-websocket/src/test/resources/keystore new file mode 100644 index 00000000000..b727bd0fb77 Binary files /dev/null and b/jetty-websocket/src/test/resources/keystore differ diff --git a/jetty-websocket/src/test/webapp/index.html b/jetty-websocket/src/test/webapp/index.html index 427e7c1522c..3f7f023ab90 100644 --- a/jetty-websocket/src/test/webapp/index.html +++ b/jetty-websocket/src/test/webapp/index.html @@ -17,7 +17,7 @@ join: function(name) { this._username=name; var location = document.location.toString().replace('http://','ws://').replace('https://','wss://'); - this._ws=new WebSocket(location,"echo-broadcast"); + this._ws=new WebSocket(location,"echo-broadcast-ping"); this._ws.onopen=this._onopen; this._ws.onmessage=this._onmessage; this._ws.onclose=this._onclose; diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml index 18bf6cb1c86..b2a39954309 100644 --- a/jetty-xml/pom.xml +++ b/jetty-xml/pom.xml @@ -2,7 +2,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 jetty-xml diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java index c14e8046964..d5f4df3915c 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -4,11 +4,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.xml; @@ -53,11 +53,11 @@ import org.xml.sax.SAXException; /** * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other * methods on the object. - * + * *

    * The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the * ServiceLoader by using the DTD and first tag element in the file. Note that DTD will be null if validation is off. - * + * */ public class XmlConfiguration { @@ -69,7 +69,7 @@ public class XmlConfiguration private static final Class[] __primitiveHolders = { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class }; private static final Integer ZERO = new Integer(0); - + private static final Iterable __factoryLoader; static { @@ -77,7 +77,7 @@ public class XmlConfiguration try { // Use reflection to look up 1.6 service loader - // loader=ServiceLoader.load(ConfigurationProcessorFactory.class); + // loader=ServiceLoader.load(ConfigurationProcessorFactory.class); Class slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader"); Method load = slc.getMethod("load",Class.class); loader=(Iterable)load.invoke(null,ConfigurationProcessorFactory.class); @@ -90,7 +90,7 @@ public class XmlConfiguration { __factoryLoader=loader; } - } + } /* ------------------------------------------------------------ */ private static XmlParser __parser; @@ -110,20 +110,22 @@ public class XmlConfiguration __parser = new XmlParser(); try { - URL configURL = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true); - __parser.redirectEntity("configure.dtd",configURL); - __parser.redirectEntity("configure_1_0.dtd",configURL); - __parser.redirectEntity("configure_1_1.dtd",configURL); - __parser.redirectEntity("configure_1_2.dtd",configURL); - __parser.redirectEntity("configure_1_3.dtd",configURL); - __parser.redirectEntity("configure_6_0.dtd",configURL); + URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true); + URL config71 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_1.dtd",true); + __parser.redirectEntity("configure.dtd",config71); + __parser.redirectEntity("configure_1_0.dtd",config60); + __parser.redirectEntity("configure_1_1.dtd",config60); + __parser.redirectEntity("configure_1_2.dtd",config60); + __parser.redirectEntity("configure_1_3.dtd",config60); + __parser.redirectEntity("configure_6_0.dtd",config60); + __parser.redirectEntity("configure_7_1.dtd",config71); - __parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",configURL); - __parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",configURL); - __parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",configURL); + __parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config71); + __parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config71); + __parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config71); - __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",configURL); - __parser.redirectEntity("-//Jetty//Configure//EN",configURL); + __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config71); + __parser.redirectEntity("-//Jetty//Configure//EN",config71); } catch (ClassNotFoundException e) { @@ -135,7 +137,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /** * Constructor. Reads the XML configuration file. - * + * * @param configuration */ public XmlConfiguration(URL configuration) throws SAXException, IOException @@ -152,7 +154,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /** * Constructor. - * + * * @param configuration * String of XML configuration commands excluding the normal XML preamble. The String should start with a " getIdMap() @@ -263,13 +265,13 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /** - * Configure an object. + * Configure an object. * *

    Apply the XML configuration script to the passed object.

    - * + * * @param obj * The object to be configured, which must be of a type or super type of the class attribute of the Configure element. - * @exception Exception + * @exception Exception */ public Object configure(Object obj) throws Exception { @@ -279,7 +281,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /** * Configure an object. If the configuration has an ID, an object is looked up by ID and it's type check. Otherwise a new object is created. - * + * * @return The newly created configured object. * @exception Exception */ @@ -335,7 +337,7 @@ public class XmlConfiguration configure(obj,_config,0); return obj; } - + /* ------------------------------------------------------------ */ private Class nodeClass(XmlParser.Node node) throws ClassNotFoundException { @@ -345,11 +347,11 @@ public class XmlConfiguration return Loader.loadClass(XmlConfiguration.class,className,true); } - + /* ------------------------------------------------------------ */ /** * Recursive configuration step. This method applies the remaining Set, Put and Call elements to the current object. - * + * * @param obj * @param cfg * @param i @@ -403,7 +405,7 @@ public class XmlConfiguration * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1. * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to * construct a suitable value from original value. @param obj - * + * * @param node */ private void set(Object obj, XmlParser.Node node) throws Exception @@ -583,7 +585,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /* * Call a put method. - * + * * @param obj @param node */ private void put(Object obj, XmlParser.Node node) throws Exception @@ -602,7 +604,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /* * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node - * + * * @return @exception Exception */ private Object get(Object obj, XmlParser.Node node) throws Exception @@ -648,7 +650,7 @@ public class XmlConfiguration * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given * class. i.e. we ignore any static methods in superclasses. @param obj - * + * * @param node @return @exception Exception */ private Object call(Object obj, XmlParser.Node node) throws Exception @@ -710,7 +712,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /* * Create a new value object. - * + * * @param obj @param node @return @exception Exception */ private Object newObj(Object obj, XmlParser.Node node) throws Exception @@ -785,7 +787,7 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /* * Reference an id value object. - * + * * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException */ private Object refObj(Object obj, XmlParser.Node node) throws Exception @@ -902,8 +904,8 @@ public class XmlConfiguration /* ------------------------------------------------------------ */ /* - * Create a new value object. - * + * Get a Property. + * * @param obj @param node @return @exception Exception */ private Object propertyObj(Object obj, XmlParser.Node node) throws Exception @@ -922,6 +924,7 @@ public class XmlConfiguration configure(prop,node,0); return prop; } + /* ------------------------------------------------------------ */ /* @@ -1091,6 +1094,14 @@ public class XmlConfiguration String defaultValue = node.getAttribute("default"); return System.getProperty(name,defaultValue); } + + if ("Env".equals(tag)) + { + String name = node.getAttribute("name"); + String defaultValue = node.getAttribute("default"); + String value=System.getenv(name); + return value==null?defaultValue:value; + } LOG.warn("Unknown value tag: " + node,new Throwable()); return null; @@ -1113,7 +1124,7 @@ public class XmlConfiguration *

    * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()} and {@link #setIdMap(Map)} . * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line. - * + * * @param args * array of property and xml configuration filenames or {@link Resource}s. */ @@ -1221,16 +1232,13 @@ public class XmlConfiguration Throwable th = exception.get(); if (th != null) { - if (th instanceof Exception) + if (th instanceof RuntimeException) + throw (RuntimeException)th; + else if (th instanceof Exception) throw (Exception)th; else if (th instanceof Error) throw (Error)th; - else if (th instanceof RuntimeException) - throw (RuntimeException)th; - else if (th instanceof ThreadDeath) - throw (ThreadDeath)th; throw new Error(th); } } - } diff --git a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd index f79d4b7f003..e2139c162ed 100644 --- a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd +++ b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd @@ -19,7 +19,7 @@ my be specified if a match is not achieved. --> - + @@ -245,6 +245,24 @@ This is equivalent to: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java index 6608b1639c8..700d7508805 100644 --- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java @@ -63,6 +63,8 @@ public class XmlConfigurationTest assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString")); assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty")); + assertEquals( "Env", System.getenv("HOME"),tc.get("Env")); + assertEquals( "Property", "xxx", tc.get("Property")); diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml index ec26b3ea944..fd0316bff67 100644 --- a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml +++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml @@ -81,6 +81,7 @@ True 2.3 + diff --git a/pom.xml b/pom.xml index b18098d9579..63315d4c21c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,11 +3,10 @@ org.eclipse.jetty jetty-parent - 18 + 19 - org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT Jetty :: Project ${jetty.url} pom @@ -186,7 +185,7 @@ org.eclipse.jetty.toolchain jetty-version-maven-plugin - 1.0.3 + 1.0.7 org.apache.maven.plugins @@ -245,7 +244,6 @@ org.codehaus.mojo findbugs-maven-plugin - 2.3.2 true true @@ -260,28 +258,29 @@ org.apache.maven.plugins maven-jxr-plugin - 2.2 org.apache.maven.plugins maven-javadoc-plugin - 2.8 + + true + true + true + org.apache.maven.plugins maven-pmd-plugin - 2.5 - - + org.apache.maven.plugins maven-jxr-plugin - 2.2 + 2.1 org.apache.maven.plugins @@ -312,6 +311,7 @@ + jetty-util jetty-jmx @@ -337,7 +338,6 @@ jetty-servlet jetty-webapp jetty-servlets - jetty-jsp-2.1 jetty-deploy jetty-ajp jetty-jndi @@ -350,9 +350,10 @@ jetty-nested jetty-overlay-deployer jetty-nosql + jetty-http-spi jetty-distribution test-continuation - test-continuation-jetty6 + test-jetty-servlet test-jetty-webapp test-jetty-nested @@ -411,6 +412,11 @@ junit ${junit-version} + + org.mockito + mockito-core + 1.8.5 + org.eclipse.jetty @@ -210,5 +209,11 @@ 2.1 provided + + javax.servlet + jstl + 1.2 + provided + diff --git a/test-jetty-webapp/src/main/java/com/acme/Dump.java b/test-jetty-webapp/src/main/java/com/acme/Dump.java index 0da3a69dbfc..063e38b3b13 100644 --- a/test-jetty-webapp/src/main/java/com/acme/Dump.java +++ b/test-jetty-webapp/src/main/java/com/acme/Dump.java @@ -28,6 +28,8 @@ import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -64,6 +66,7 @@ public class Dump extends HttpServlet private static final Logger LOG = Log.getLogger(Dump.class); boolean fixed; + Timer _timer; /* ------------------------------------------------------------ */ @Override @@ -77,6 +80,8 @@ public class Dump extends HttpServlet fixed=true; throw new UnavailableException("Unavailable test",Integer.parseInt(config.getInitParameter("unavailable"))); } + + _timer=new Timer(true); } /* ------------------------------------------------------------ */ @@ -168,40 +173,26 @@ public class Dump extends HttpServlet request.setAttribute("RESUME",Boolean.TRUE); final long resume=Long.parseLong(request.getParameter("resume")); - new Thread(new Runnable() + final Continuation continuation = ContinuationSupport.getContinuation(request); + _timer.schedule(new TimerTask() { + @Override public void run() { - try - { - Thread.sleep(resume); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } - Continuation continuation = ContinuationSupport.getContinuation(request); continuation.resume(); } - - }).start(); + },resume); + } if (request.getParameter("complete")!=null) { final long complete=Long.parseLong(request.getParameter("complete")); - new Thread(new Runnable() + _timer.schedule(new TimerTask() { + @Override public void run() { - try - { - Thread.sleep(complete); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } try { response.setContentType("text/html"); @@ -209,13 +200,12 @@ public class Dump extends HttpServlet Continuation continuation = ContinuationSupport.getContinuation(request); continuation.complete(); } - catch (IOException e) + catch(Exception e) { e.printStackTrace(); } } - - }).start(); + },complete); } if (request.getParameter("suspend")!=null && request.getAttribute("SUSPEND")!=Boolean.TRUE) @@ -796,7 +786,7 @@ public class Dump extends HttpServlet } catch (Exception e) { - getServletContext().log("dump", e); + getServletContext().log("dump "+e); } String lines= request.getParameter("lines"); @@ -839,6 +829,7 @@ public class Dump extends HttpServlet @Override public synchronized void destroy() { + _timer.cancel(); } /* ------------------------------------------------------------ */ diff --git a/test-jetty-webapp/src/main/java/com/acme/TestFilter.java b/test-jetty-webapp/src/main/java/com/acme/TestFilter.java index 3712a2c7db4..18758dd276c 100644 --- a/test-jetty-webapp/src/main/java/com/acme/TestFilter.java +++ b/test-jetty-webapp/src/main/java/com/acme/TestFilter.java @@ -73,7 +73,7 @@ public class TestFilter implements Filter String to = request.getServerName(); String path=((HttpServletRequest)request).getServletPath(); - if (!_remote && !_allowed.contains(path) && ( + if (!"/remote.html".equals(path) && !_remote && !_allowed.contains(path) && ( !from.equals("localhost") && !from.startsWith("127.") && from.indexOf(":1")<0 || !to.equals("localhost")&&!to.startsWith("127.0.0.") && to.indexOf(":1")<0)) { diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml b/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml index eee0a8801b3..c08ca7fdbd9 100644 --- a/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml +++ b/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml @@ -36,7 +36,7 @@ org.eclipse.jetty.servlets.QoSFilter maxRequests - 20 + 10000 managedAttr @@ -235,7 +235,7 @@ TransparentProxy org.eclipse.jetty.servlets.ProxyServlet$Transparent - Prefix/javadoc + Prefix/javadoc-proxy ProxyTohttp://download.eclipse.org/jetty/stable-7/apidocs @@ -247,9 +247,20 @@ TransparentProxy - /javadoc/* + /javadoc-proxy/* + + + + foo.jsp + /jsp/foo/foo.jsp + + + foo.jsp + /jsp/foo/ + + 404 /error404.html diff --git a/test-jetty-webapp/src/main/webapp/index.html b/test-jetty-webapp/src/main/webapp/index.html index 28f1b26eeb7..74ef907a75a 100644 --- a/test-jetty-webapp/src/main/webapp/index.html +++ b/test-jetty-webapp/src/main/webapp/index.html @@ -3,6 +3,11 @@ Powered By Jetty + @@ -11,8 +16,8 @@ This is the Test webapp for the Jetty 7 HTTP Server and Servlet Container. For more information about Jetty, please visit our website -or wiki. -Commercial support for Jetty is available via webtide. +or wiki or see the bundled javadoc.
    +Commercial support for Jetty is available via Webtide and Intalio.

    This is a test context that serves: @@ -29,7 +34,7 @@ This is a test context that serves:

  • Dispatcher Servlet
  • Request Rewrite Servlet
  • JSP examples
  • -
  • Transparent Proxy (to javadoc @ eclipse.org)
  • +
  • Transparent Proxy (to javadoc @ eclipse.org)
  • Comet Chat: Long Polling, WebSocket
  • Authentication
  • diff --git a/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp b/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp new file mode 100644 index 00000000000..7ec8955932d --- /dev/null +++ b/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp @@ -0,0 +1,15 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + +

    FOO Example

    +
    +

    A trivial FOO example +


    + + +
    +
    + + diff --git a/test-jetty-webapp/src/main/webapp/jsp/index.html b/test-jetty-webapp/src/main/webapp/jsp/index.html index 935f85bc7a4..644ebc7ff40 100644 --- a/test-jetty-webapp/src/main/webapp/jsp/index.html +++ b/test-jetty-webapp/src/main/webapp/jsp/index.html @@ -10,6 +10,8 @@
  • JSP 2.0 SimpleTag demo
  • JSP 2.0 Tag File demo
  • JSP 2.0 Tag Expression
    +
  • JSTL Expression
    +
  • Mapping to <jsp-file>
    Main Menu diff --git a/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp b/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp new file mode 100644 index 00000000000..9fa7b57e96c --- /dev/null +++ b/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp @@ -0,0 +1,15 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + +

    JSTL Example

    +
    +

    A trivial jstl example +


    + + +
    +
    + + diff --git a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java index c5fb5d9d0ac..8e87ee83df1 100644 --- a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java +++ b/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java @@ -21,7 +21,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.ssl.SslContextFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.Handler; diff --git a/tests/pom.xml b/tests/pom.xml index e586ce49bc0..28cee13d78b 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty jetty-project - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT org.eclipse.jetty.tests tests-parent @@ -37,14 +37,6 @@ true - - org.apache.maven.plugins - maven-deploy-plugin - - - true - - diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml index 547acddf835..bf1daf62b96 100644 --- a/tests/test-integration/pom.xml +++ b/tests/test-integration/pom.xml @@ -20,7 +20,7 @@ org.eclipse.jetty.tests tests-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT 4.0.0 test-integration diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java index 60a15693333..bb491353fc1 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java @@ -16,8 +16,6 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.client.security.SimpleRealmResolver; import org.eclipse.jetty.http.HttpMethods; -import org.eclipse.jetty.http.security.Constraint; -import org.eclipse.jetty.http.security.Password; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; @@ -33,6 +31,8 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java index ae4ed46a43e..60cc571af76 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java @@ -1075,8 +1075,6 @@ public abstract class RFC2616BaseTest specId = "10.3 Redirection HTTP/1.0 - basic"; response.assertStatus(specId,HttpStatus.FOUND_302); response.assertHeader(specId,"Location",serverURI + "/tests/"); - response.assertHeader(specId,"Connection","close"); - } /** @@ -1136,9 +1134,6 @@ public abstract class RFC2616BaseTest String specId = "10.3 Redirection HTTP/1.0 w/content"; response.assertStatus(specId,HttpStatus.FOUND_302); response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/R1.txt"); - response.assertHeader(specId,"Connection","close"); - response.assertHeaderNotPresent(specId,"Content-Length"); - } /** @@ -1803,7 +1798,6 @@ public abstract class RFC2616BaseTest response = responses.get(1); // response 2 response.assertStatusOK(specId); - response.assertHeader(specId,"Connection","close"); response.assertBodyContains(specId,"Resource=R2"); /* Compatibility with HTTP/1.0 */ @@ -1849,7 +1843,6 @@ public abstract class RFC2616BaseTest response = responses.get(2); response.assertStatusOK(specId); - response.assertHeader(specId,"Connection","close"); response.assertBody(specId,"Host=Default\nResource=R2\n"); } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java index ebdc675d71f..beaf97cd2de 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java @@ -44,4 +44,9 @@ public class RFC2616NIOHttpsTest extends RFC2616BaseTest { return new HttpsSocketImpl(); } + + public void test8_2_ExpectInvalid() throws Exception + { + super.test8_2_ExpectInvalid(); + } } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/EchoHandler.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/EchoHandler.java index a1ea78406a6..2ed885db932 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/EchoHandler.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/EchoHandler.java @@ -24,7 +24,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -32,8 +31,7 @@ public class EchoHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - Request base_request = (request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest(); - base_request.setHandled(true); + baseRequest.setHandled(true); if (request.getContentType() != null) response.setContentType(request.getContentType()); diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java index 3a071047195..332aa4fed39 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpsSocketImpl.java @@ -25,6 +25,7 @@ import java.net.SocketAddress; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -78,7 +79,7 @@ public class HttpsSocketImpl implements HttpSocket try { // TODO real trust manager - this.sslContext = SSLContext.getInstance("SSL"); + this.sslContext = SSLContext.getInstance("TLS"); sslContext.init(null,trustAllCerts,new java.security.SecureRandom()); } catch (Exception e) @@ -87,11 +88,13 @@ public class HttpsSocketImpl implements HttpSocket } sslfactory = sslContext.getSocketFactory(); + } public Socket connect(InetAddress host, int port) throws IOException { - Socket sslsock = sslfactory.createSocket(); + SSLSocket sslsock = (SSLSocket)sslfactory.createSocket(); + sslsock.setEnabledProtocols(new String[] {"TLSv1"}); SocketAddress address = new InetSocketAddress(host,port); sslsock.connect(address); return sslsock; diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml index 619cce3db29..a5e08c91017 100644 --- a/tests/test-loginservice/pom.xml +++ b/tests/test-loginservice/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty.tests tests-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-loginservice Jetty Tests :: Login Service diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java index 6eb658dd57e..122a8f2fe76 100644 --- a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java +++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java @@ -44,7 +44,7 @@ import org.eclipse.jetty.client.security.Realm; import org.eclipse.jetty.client.security.SimpleRealmResolver; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.security.ConstraintMapping; diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index 4dc47848000..8c829fa6935 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty.tests tests-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-sessions-parent Jetty Tests :: Sessions :: Parent diff --git a/tests/test-sessions/test-hash-sessions/pom.xml b/tests/test-sessions/test-hash-sessions/pom.xml index 985e436e1a0..1152f8a40e2 100644 --- a/tests/test-sessions/test-hash-sessions/pom.xml +++ b/tests/test-sessions/test-hash-sessions/pom.xml @@ -21,20 +21,28 @@ org.eclipse.jetty.tests test-sessions-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-hash-sessions Jetty Tests :: Sessions :: Hash - - - maven-compiler-plugin - - 1.5 - 1.5 - - - + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java index 6ddbf791e07..720d90f3562 100644 --- a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java +++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java @@ -36,7 +36,7 @@ public class HashTestServer extends AbstractTestServer } - public SessionIdManager newSessionIdManager() + public SessionIdManager newSessionIdManager(String config) { return new HashSessionIdManager(); } diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml index 55e93291123..567cfad6daa 100644 --- a/tests/test-sessions/test-jdbc-sessions/pom.xml +++ b/tests/test-sessions/test-jdbc-sessions/pom.xml @@ -21,20 +21,28 @@ org.eclipse.jetty.tests test-sessions-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-jdbc-sessions Jetty Tests :: Sessions :: JDBC - - - maven-compiler-plugin - - 1.5 - 1.5 - - - + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java index c602cc7382a..6e6cb0fa35b 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java @@ -22,22 +22,29 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; public class JdbcTestServer extends AbstractTestServer { public static final String DRIVER_CLASS = "org.apache.derby.jdbc.EmbeddedDriver"; - public static final String CONNECTION_URL = "jdbc:derby:sessions;create=true"; + public static final String DEFAULT_CONNECTION_URL = "jdbc:derby:sessions;create=true"; public static final int SAVE_INTERVAL = 1; + static { System.setProperty("derby.system.home", MavenTestingUtils.getTargetFile("test-derby").getAbsolutePath()); } + public JdbcTestServer(int port) { super(port); } - + + public JdbcTestServer(int port, int maxInactivePeriod, int scavengePeriod, String connectionUrl) + { + super(port, maxInactivePeriod, scavengePeriod, connectionUrl); + } + public JdbcTestServer(int port, int maxInactivePeriod, int scavengePeriod) { - super(port, maxInactivePeriod, scavengePeriod); + super(port, maxInactivePeriod, scavengePeriod, DEFAULT_CONNECTION_URL); } public JdbcTestServer (int port, boolean optimize) @@ -60,14 +67,14 @@ public class JdbcTestServer extends AbstractTestServer * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager() */ @Override - public SessionIdManager newSessionIdManager() + public SessionIdManager newSessionIdManager(String config) { synchronized(JdbcTestServer.class) { JDBCSessionIdManager idManager = new JDBCSessionIdManager(_server); idManager.setScavengeInterval(_scavengePeriod); idManager.setWorkerName("w"+(__workers++)); - idManager.setDriverInfo(DRIVER_CLASS, CONNECTION_URL); + idManager.setDriverInfo(DRIVER_CLASS, (config==null?DEFAULT_CONNECTION_URL:config)); return idManager; } } diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml index ac94a5a9f71..2979f679288 100644 --- a/tests/test-sessions/test-sessions-common/pom.xml +++ b/tests/test-sessions/test-sessions-common/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty.tests test-sessions-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-sessions-common Jetty Tests :: Sessions :: Common diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java index a4329716488..7742d16d411 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java @@ -3,12 +3,16 @@ package org.eclipse.jetty.server.session; import static org.junit.Assert.*; import java.io.IOException; +import java.util.EventListener; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.HttpClient; @@ -30,6 +34,8 @@ public abstract class AbstractRemoveSessionTest AbstractTestServer server = createServer(0, 1, scavengePeriod); ServletContextHandler context = server.addContext(contextPath); context.addServlet(TestServlet.class, servletMapping); + TestEventListener testListener = new TestEventListener(); + context.getSessionHandler().addEventListener(testListener); server.start(); int port = server.getPort(); try @@ -49,6 +55,8 @@ public abstract class AbstractRemoveSessionTest assertTrue(sessionCookie != null); // Mangle the cookie, replacing Path with $Path, etc. sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + //ensure sessionCreated listener is called + assertTrue (testListener.isCreated()); //now delete the session exchange = new ContentExchange(true); @@ -58,6 +66,8 @@ public abstract class AbstractRemoveSessionTest client.send(exchange); exchange.waitForDone(); assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus()); + //ensure sessionDestroyed listener is called + assertTrue(testListener.isDestroyed()); // The session is not there anymore, but we present an old cookie @@ -106,4 +116,32 @@ public abstract class AbstractRemoveSessionTest } } + public static class TestEventListener implements HttpSessionListener + { + boolean wasCreated; + boolean wasDestroyed; + + public void sessionCreated(HttpSessionEvent se) + { + wasCreated = true; + } + + public void sessionDestroyed(HttpSessionEvent se) + { + wasDestroyed = true; + } + + public boolean isDestroyed() + { + return wasDestroyed; + } + + + public boolean isCreated() + { + return wasCreated; + } + + } + } diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java index 87cba0cb0fb..c2ee3ea4816 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java @@ -34,22 +34,31 @@ public abstract class AbstractTestServer protected final ContextHandlerCollection _contexts; protected SessionIdManager _sessionIdManager; + + public AbstractTestServer(int port) { this(port, 30, 10); } public AbstractTestServer(int port, int maxInactivePeriod, int scavengePeriod) + { + this (port, maxInactivePeriod, scavengePeriod, null); + } + + public AbstractTestServer(int port, int maxInactivePeriod, int scavengePeriod, String sessionIdMgrConfig) { _server = new Server(port); _maxInactivePeriod = maxInactivePeriod; _scavengePeriod = scavengePeriod; _contexts = new ContextHandlerCollection(); - _sessionIdManager = newSessionIdManager(); + _sessionIdManager = newSessionIdManager(sessionIdMgrConfig); + _server.setSessionIdManager(_sessionIdManager); } + + - - public abstract SessionIdManager newSessionIdManager(); + public abstract SessionIdManager newSessionIdManager(String config); public abstract SessionManager newSessionManager(); public abstract SessionHandler newSessionHandler(SessionManager sessionManager); diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml index eeee5f5187e..c34dda4cdc9 100644 --- a/tests/test-webapps/pom.xml +++ b/tests/test-webapps/pom.xml @@ -21,12 +21,22 @@ org.eclipse.jetty.tests tests-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-webapps-parent Jetty Tests :: WebApps :: Parent pom + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + test-webapp-rfc2616 diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml index 2ad2385f18f..fcc140014f9 100644 --- a/tests/test-webapps/test-webapp-rfc2616/pom.xml +++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml @@ -21,12 +21,22 @@ org.eclipse.jetty.tests test-webapps-parent - 7.5.0-SNAPSHOT + 7.6.0-SNAPSHOT test-webapp-rfc2616 Jetty Tests :: WebApp :: RFC2616 war + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + +