diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..e1d1cc748c6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/VERSION.txt b/VERSION.txt index 8e06c966a89..65b5ef8f81e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,6 +1,67 @@ jetty-10.0.0-SNAPSHOT -jetty-10.0.0.beta0 - 27 May 2020 +jetty-10.0.0.beta1 - 10 July 2020 + + 1100 JSR356 Encoder#init is not called when created on demand + + 2540 Flaky test: org.eclipse.jetty.client.ConnectionPoolTest + + 3428 Support Decoder lists on javax.websocket endpoints + + 4741 getHttpServletMapping for async dispatch + + 4776 Incorrect path matching for WebSocket using PathMappings + + 4826 Upgrade to Apache Jasper 8.5.54 + + 4855 occasional h2spec failures on jenkins + + 4877 Review PathSpec classes + + 4885 setCookie() must not change the headers in a response during an include + + 4890 JettyClient behavior when SETTINGS_HEADER_TABLE_SIZE is set to 0 in + SETTINGS Frame. + + 4903 Give better errors for non public Websocket Endpoints + + 4904 WebsocketClient creates more connections than needed + + 4907 + org.eclipse.jetty.websocket.tests.SuspendResumeTest#testSuspendAfterClose + + 4913 DirectoryNotEmptyException when using mvn jetty:run-distro + + 4920 Restore ability to delete sessions on stop + + 4921 Quickstart run improperly runs dynamically added context initializers + + 4923 SecureRequestCustomizer.SslAttributes does not cache cert chain like + before + + 4929 HttpClient: HttpCookieStore.Empty prevents sending cookies + + 4936 Response header overflow leads to buffer corruptions + + 4965 WINDOW_UPDATE for locally failed stream should not close the HTTP/2 + session + + 4967 Possible buffer corruption in HTTP/2 session failures + + 4971 Simplify Connection.upgradeFrom()/upgradeTo() + + 4976 HttpClient async content throws NPE in DEBUG log + + 4981 Incorrect example for TryFilesFilter API docs + + 4985 NPE related to WebSocket with Vaadin / Atmosphere after switching from + 9.4.26 to 9.4.30 + + 4989 annotation get NPE when parse library contain module-info.class + (example jakarta.xml.ws-api_2.3.2.jar) + + 5000 NPE from Server.dump of FilterMapping + + 5018 WebSocketClient upgrade request timeout not configurable + +jetty-9.4.31.v20200723 - 23 July 2020 + + 1100 JSR356 Encoder#init is not called when created on demand + + 4736 Update Import-Package version start ranges + + 4890 JettyClient behavior when SETTINGS_HEADER_TABLE_SIZE is set to 0 in + SETTINGS Frame. + + 4904 WebsocketClient creates more connections than needed + + 4965 WINDOW_UPDATE for locally failed stream should not close the HTTP/2 + session + + 4967 Possible buffer corruption in HTTP/2 session failures + + 4971 Simplify Connection.upgradeFrom()/upgradeTo() + + 4976 HttpClient async content throws NPE in DEBUG log + + 4981 Incorrect example for TryFilesFilter API docs + + 4985 Fix NPE related to use of Attributes.Wrapper getAttributeNameSet() + + 4989 Prevent parsing of module-info.class in OSGi bundles + + 5000 NPE from Server.dump of FilterMapping + + 5013 Bundle-ClassPath and lib place on WEB-INF/lib make classpath duplicate + + 5018 WebSocketClient connect / upgrade timeout not configurable + + 5019 Automatically hot-reload SSL certificates if keystore file changed + + 5020 LifeCycle.Listener not called for Filter/Servlet/Listener lifecycle + events + + 5025 dispatcher.include() with welcome files lead to stack overflow error + + 5053 CWE-331 in DigestAuthentication class + + 5057 `javax.servlet.include.context_path` attribute on root context. should + be empty string, but is `"/"` + + 5064 NotSerializableException for OpenIdConfiguration + + 5069 HttpClientTimeoutTests can occasionally fail due to unreachable network jetty-9.4.30.v20200611 - 11 June 2020 + 4776 Incorrect path matching for WebSocket using PathMappings @@ -48,361 +109,6 @@ jetty-9.4.29.v20200521 - 21 May 2020 + 4895 AbstractSessionCache.setFlushOnResponseCommit(true) can write an invalid session to the backing store -jetty-10.0.0.alpha1 - 26 November 2019 - + 97 Permanent UnavailableException thrown during servlet request handling - should cause servlet destroy - + 137 Support OAuth - + 155 No way to set keystore for JSR 356 websocket clients, needed for SSL - client authentication - + 250 Implement HTTP CONNECT for HTTP/2 - + 995 UrlEncoded.encodeString should skip more characters - + 1036 Allow easy configuration of Scheduler-Threads and name them more - appropriate - + 1485 Add systemd service file - + 1743 Refactor jetty maven plugin goals to be more orthogonal - + 2266 Jetty maven plugin reload is triggered each time the - `scanIntervalSeconds` pass - + 2340 Remove raw ServletHandler usage examples from documentation - + 2429 Review HttpClient backpressure semantic - + 2578 Use addEventListener(EventListener listener) - + 2709 current default for headerCacheSize is not large enough for many - requests - + 2815 hpack fields are opaque octets - + 3040 Allow RFC6265 Cookies to include optional SameSite attribute - + 3083 The ini-template for jetty.console-capture.dir does not match the - default value - + 3106 Websocket connection stats and request stats - + 3558 Error notifications can be received after a successful websocket close - + 3601 HTTP2 stall on reset streams - + 3705 Review ClientUpgradeRequest exception handling - + 3734 websocket suspend when input closed - + 3747 Make Jetty Demo work with JPMS - + 3787 Jetty client sometimes returns EOFException instead of - SSLHandshakeException on certificate errors. - + 3804 Weld/CDI XML backwards compat? - + 3806 Error Page handling Async race with ProxyServlet - + 3822 trustAll will not work on some servers - + 3829 Avoid sending empty trailer frames for http/2 responses - + 3840 Byte-range request performance problems with large files - + 3856 Different behaviour with maxFormContentSize=0 if Content-Length header - is present/missing - + 3863 Enforce use of SNI - + 3869 Update to ASM 7.2 for jdk 13 - + 3872 Review exposure of JavaxWebSocketServletContainerInitializer - + 3876 WebSocketPartialListener is only called for initial frames, not for - continuation frames - + 3884 @WebSocket without @OnWebSocketMessage handler fails when receiving a - continuation frame - + 3888 BufferUtil.toBuffer(Resource resource,boolean direct) does not like - large (4G+) Resources - + 3906 Fix for #3840 breaks Path encapsulation in PathResource - + 3913 Clustered HttpSession IllegalStateException: Invalid for read - + 3929 Deadlock between new HTTP2Connection() and Server.stop() - + 3936 Race condition when modifying session + sendRedirect() - + 3940 Double initialization of Log - + 3951 Consider adding demand API to HTTP/2 - + 3952 Server configuration for direct/heap ByteBuffers - + 3956 Remove and warn on use of illegal HTTP/2 response headers - + 3957 CustomRequestLog bad usage of MethodHandles.lookup() - + 3960 Fix HttpConfiguration copy constructor - + 3964 Improve efficiency of listeners - + 3968 WebSocket sporadic ReadPendingException using suspend/resume - + 3969 X-Forwarded-Port header customization isn't possible - + 3978 HTTP/2 fixes for robustly handling abnormal traffic and resource - exhaustion - + 3983 JarFileResource incorrectly lists the contents of directories with - spaces - + 3985 Improve lenient Cookie parsing - + 3989 Inform custom ManagedSelector of dead selector via optional - onFailedSelect() - + 4000 Add SameFileAliasChecker to help with FileSystem static file access - normalization on Mac and Windows - + 4003 Quickstart broken in jetty-10 - + 4007 Getting NullPointerException while trying to run jetty start.run on - Windows - + 4009 ServletContextHandler setSecurityHandler broke handler chain - + 4020 Revert WebSocket ExtensionFactory change to interface - + 4022 Servlet which is added by ServletRegistration can't be started - + 4025 Provide more write-through behaviours for DefaultSessionCache - + 4027 Ensure AbstractSessionDataStore cannot be used unless it is started - + 4033 Ignore bad percent encodings in paths during - URIUtil.equalsIgnoreEncodings() - + 4047 Gracefully stopped Jetty not flushing all response data - + 4048 Multiple values in X-Forwarded-Port throw NumberFormatException - + 4057 NullPointerException in o.e.j.h.HttpFields - + 4058 Review Locker - + 4064 java.lang.NullPointerException initializing embedded servlet - + 4075 Do not fail on servlet-mapping with url-pattern /On* - + 4076 Restarting quickstarted webapp throws IllegalStateException: - ServletContainerInitializersStarter already exists - + 4082 Debug logging causes NullPointerException in client - + 4084 Use of HttpConfiguration.setBlockingTimeout(long) in jetty.xml produces - warning on jetty-home startup - + 4096 Thread in ReservedThreadExecutor does not exit when stopped - + 4104 Frames are sent through ExtensionStack even if WebSocket Session is - closed - + 4105 QueuedThreadPool increased thread usage and no idle thread decay - + 4113 HttpClient fails with JDK 13 and TLS 1.3 - + 4115 Drop HTTP/2 pseudo headers - + 4121 QueuedThreadPool should support ThreadFactory behaviors - + 4122 QueuedThreadPool should reset thread interrupted on failed run - + 4124 Run websocket autobahn tests with jetty and javax apis instead of just - with core. - + 4128 OpenIdCredentials can't decode JWT ID token - + 4132 Should be possible to use OIDC without metadata - + 4138 OpenID module should use HttpClient instead of HttpURLConnection - + 4141 ClassCastException with non-async Servlet + async Filter + - HttpServletRequestWrapper - + 4142 Configurable HTTP/2 RateControl - + 4144 Naked cast to Request should be avoided - + 4150 Module org.eclipse.jetty.alpn.client not found, required by - org.eclipse.jetty.proxy - + 4152 WebSocket autoFragment does not fragment based on maxFrameSize - + 4156 IllegalStateException when forwarding to jsp with new session - + 4161 Regression: EofException: request lifecycle violation - + 4170 Client-side alias selection based on SSLEngine - + 4173 NullPointerException warning in log from WebInfConfiguration after - upgrade - + 4174 ConcurrentModificationException when stopping jetty:run-war - + 4176 Should not set header if sendError has been called - + 4177 Configure HTTP proxy with SslContextFactory - + 4179 Improve HttpChannel$SendCallback references for GC - + 4183 Jetty considers bootstrap injected class to be a "server class" - + 4188 Spin in HttpOutput.close - + 4190 Jetty hangs after thread blocked in SharedBlockingCallback.block() - called by HttpOutput.close - + 4191 Increase GzipHandler minGzipSize default value - + 4193 InetAccessHandler - new includeConnectors/excludeConnectors not quite - correct anymore - + 4201 Throw SSLHandshakeException in case of TLS handshake failures - + 4203 Some Transfer-Encoding and Content-Length combinations do not result in - expected 400 Bad Request - + 4204 Transfer-Encoding behavior does not follow RFC7230 - + 4208 304 response with Content-Length fails, not conform to RFC7230 - + 4209 Unused TLS connection is not closed in Java 11 - + 4217 SslConnection.DecryptedEnpoint.flush eternal busy loop - + 4222 Major/Minor Version wrong (jetty 10 is servlet 4) - + 4227 First authorization request produced by OIDC module fails due to - inclusion of sessionid - + 4236 clean up redirect code calculation for OpenIdAuthenticator - + 4237 simplify openid module configuration - + 4240 CGI form post results in 500 response if no character encoding - + 4243 ErrorHandler produces invalid json error response - + 4247 Cookie security attributes are going to mandated by Google Chrome - + 4248 Websocket client UpgradeListener never reports success - + 4251 Http 2.0 clients cannot upgrade protocol - + 4258 RateControl should be per-connection - + 4264 Spring Boot BasicErrorController no longer invoked - + 4265 HttpChannel SEND_ERROR should use ErrorHandler.doError() - + 4277 Reading streamed gzipped body never terminates - + 4279 Regression: ResponseWriter#close blocks indefinitely - + 4282 Review HttpParser handling in case of no content - + 4283 Wrong package for OpenJDK8ClientALPNProcessor - + 4284 Possible NullPointerException in Main.java when stopped from command - line - + 4287 Move getUriLastPathSegment(URI uri) to URIUtil - + 4296 Unable to create WebSocket connect if the query string of the URL has % - symbol. - + 4301 Demand beforeContent is not forwarded - + 4305 Jetty server ALPN shall alert fatal no_application_protocol if no - client application protocol is supported - + 4325 Deprecate SniX509ExtendedKeyManager constructor without - SslContextFactory$Server - + 4334 Better test ErrorHandler changes - + 4342 OpenID module cannot create HttpClient in Jetty 10 - -jetty-10.0.0-alpha0 - 11 July 2019 - + 113 Add support for NCSA Extended Log File Format - + 114 Bring back overlay deployer - + 132 ClientConnector abstraction - + 207 Support javax.websocket version 1.1 - + 215 Add Conscrypt for native ALPN/TLS/SSL - + 300 Implement Deflater / Inflater Object Pool - + 482 jetty-osgi] The CCL while parsing the xml files should be set to a - combination of Jetty and Bundle-Classloader - + 592 Support no-value Host header in HttpParser - + 632 JMX tests rely on fixed port - + 675 Slf4jLog.ignore() should produce at DEBUG level - + 676 JavaUtilLog.ignore() should produce at DEBUG level - + 677 Logging of .ignore() should indicate that it was an "Ignored Exception" - + 746 Implement Servlet 4.0 Request.getMappings - + 801 Jetty respond with status 200 instead of 304 while using Servlet 4.0 - PushBuilder - + 809 NPE in WebInfConfiguration for webapp deploy in osgi - + 987 Can GzipHandler check if .gz file exists only by some paths? - + 1135 Avoid allocations from Method.getParameterTypes() if possible - + 1200 Use PathWatcher in DeploymentManager - + 1350 Dynamic selection of the transport to use based on ALPN on the client - side - + 1368 Need to support KeyStore/TrustStore with null passwords - + 1384 Expose StatisticsServlet to webapp - + 1468 Configure PKIX Revocation Checker for SslContextFactory - + 1485 Add systemd service file - + 1498 Add JRTResource to support future Java 9 classloader behaviors - + 1499 ClasspathPattern needs MODULE ruleset to support future Java 9 - classloader behaviors - + 1503 IPv6 address needs normalization (without brackets) in - ForwardedRequestCustomizer - + 1551 Move CookieCutter to jetty-http - + 1571 Support Hazelcast session management in 9.4 - + 1574 TypeUtilTest#testGetLocationOfClass is user settings dependant - + 1591 JDBCSessionDataStore doesn't work with root context on Oracle DB - + 1592 CompressedContentFormat.tagEquals() - incorrect comparison of entity - tag hashes - + 1595 HTTP/2: Avoid sending unnecessary stream WINDOW_UPDATE frames - + 1599 WebSocketCloseTest fails - + 1600 Update jndi.mod and plus.mod - + 1603 WebSocketServerFactory NPE in toString() - + 1604 WebSocketContainer stop needs improvement - + 1605 ContainerProvider.getWebSocketContainer() behavior is not to spec - + 1615 Password defaults in jetty-ssl-context.xml should be removed - + 1618 AsyncContext.dispatch() does not use raw/encoded URI - + 1622 HeaderFilter doesn't work if the response has been committed - + 1623 JettyRunMojo use dependencies from reactor (outputdirectory) - + 1625 Support new IANA declared Websocket Close Status Codes - + 1637 Thread per connection retained in HTTP/2 - + 1638 Add it test for Maven Plugin - + 1642 Using RewriteHandler with AsyncContext.dispatch() and - HttpServletRequestWrapper not possible - + 1643 ProxyServlet always uses default number of selector threads - - constructor should allow to overwrite the default. - + 1645 NotSerializableException: DoSFilter when using Non-Clustered Session - Management: File System - + 1656 Improve configurability of ConnectionPools - + 1671 Asymmetric usage of trailers in MetaData.Request - + 1675 Session id should not be logged with INFO level in AbstractSessionCache - + 1676 Remove Deprecated classes & methods - + 1679 DeploymentManagerMBean not usable through JMX - + 1682 Jetty-WarFragmentFolderPath directive has no effect in eclipse runtime - mode except for the first launch - + 1692 Annotation scanning should ignore `module-info.class` files - + 1698 Missing WWW-Authenticate from SpnegoAuthenticator when other - Authorization header provided - + 1746 Remove LICENSE-CONTRIBUTOR? - + 1836 Migrate Locker implementation to JVM ReentrantLock implementation - + 1838 Servlet 4.0.0 artifact now available on central.maven.org - + 1852 Fix quickstart generation for servlet 4.0 - + 1898 Request.getCookie() should ignore invalid cookies - + 1956 Store and report build information of Jetty - + 1977 jetty-http-spi tests fail with java9 - + 2061 WebSocket hangs in blockingWrite - + 2075 Deprecating MultiException - + 2095 Remove FastCGI multiplexing - + 2103 Server should open connectors early in start sequence - + 2108 Update licence headers and plugin for 2018 - + 2140 Infinispan and hazelcast changes to scavenge zombie expired sessions - + 2172 Support javax.websocket 1.1 - + 2175 Refactor WebSocket close handling - + 2191 JPMS Support - + 2431 Upgrade to Junit 5 - + 2868 Adding SPNEGO authentication support for Jetty Client - + 2901 Introduce HttpConnectionUpgrader as a conversation component in - HttpClient - + 2909 Remove B64Code - + 2948 Require JDK 11 for Jetty 10.x - + 2978 Add module-info.java to relevant Jetty modules - + 2983 Jetty 10 Configuration abstraction - + 2985 Jetty 10 Configuration replacement algorithm incorrect - + 2996 ContextHandler.setDefaultContextPath() not implemented for quickstart - + 3009 Update Jetty 10 to use non-LEGACY Compliance Modes - + 3010 Move old MultiPart parsing implementation to jetty-http - + 3011 Move HttpCompliance to HttpConfiguration - + 3012 Refactor HttpCompliance and HttpComplianceSection to be friendlier to - customization - + 3129 javax-websocket-common pom.xml is wrong - + 3139 NPE on - WebSocketServerContainerInitializer.configureContext(ServletContextHandler) - + 3154 Add support for javax.net.ssl.HostnameVerifier to HttpClient - + 3159 WebSocket permessage-deflate RSV1 validity check - + 3162 Use Jetty specific Servlet API jar - + 3165 Review javax-websocket-server tests - + 3166 Run of autobahn websocket tests on CI - + 3167 JavaxWebSocketServerContainerInitializer always creates a HttpClient - + 3170 WebSocket proxy PoC - + 3182 Restore websocket example files - + 3186 Jetty maven plugin - javax.annotation.jar picked up from jetty plugin - rather than from applications classpath - + 3197 Use jetty specific websocket API jar - + 3213 MetaInfConfigurationTest tests disabled in jetty-10.0.x - + 3216 Autobahn WebSocketServer failures in jetty 10 - + 3225 Response.sendError should not set reason - + 3246 javax-websocket-tests exception stacktraces - + 3249 Update to apache jasper 9.0.14 for jetty-10 - + 3274 OSGi versions of java.base classes in - org.apache.felix:org.osgi.foundation:jar conflicts with new rules on Java 9+ - + 3279 WebSocket write may hang forever - + 3288 Correct websocket artifactIds on jetty-10.0.x - + 3290 async websocket onOpen, onError and onClose in 10.0.x - + 3298 Review jetty-10 websocket CompletableFuture usage - + 3303 Update to jakarta ee javax artifacts for jetty-10 - + 3308 Remove deprecated methods from sessions - + 3320 Review Jetty 10 module-info.java - + 3333 Jetty 10 standalone cannot start on the module-path - + 3340 Update PushCacheFilter to use Servlet 4.0 APIs - + 3341 XmlBasedHttpClientProvider in Jetty 10 - + 3351 Restructure jetty-unixsocket module - + 3374 JSR356 RemoteEndpoint.Async.setSendTimeout() logic invalid in Jetty - 10.0.x - + 3379 Tracking of WebSocket Sessions in WebSocket containers - + 3380 WebSocket should support jetty-io Connection.Listener - + 3382 Jetty WebSocket Session.suspend() not implemented - + 3399 XmlConfiguration jetty.webapps.uri is the uri of the webapp not the - parent dir - + 3406 restore and fix jetty websocket tests in jetty 10 - + 3412 problems with jetty 10 WebSocket session customizer - + 3446 allow jetty WebSockets to be upgraded using WebSocketUpgradeFilter in - jetty-10 - + 3453 Removing moved Extension classes from jetty-websocket-api - + 3458 ensure users of the jetty-websocket-api do not have to see - websocket-core classes - + 3462 client validation of websocket upgrade response - + 3465 websocket negotiation of extension configuration parameters - + 3479 review and cleanup of jetty-websocket-api in jetty-10 - + 3484 ClassCastException when using websocket-core classes in - websocket-servlet - + 3494 flaky tests in jetty-websocket-tests ClientCloseTest - + 3564 Update jetty-10.0.x to apache jsp 9.0.19 - + 3608 Reply with 400 Bad request to malformed WebSocket handshake - + 3616 Backport WebSocket SessionTracker from Jetty 10 - + 3648 javax.websocket client container incorrectly creates Server - SslContextFactory - + 3661 JettyWebSocketServerContainer exposes websocket common classes - + 3666 WebSocket - Handling sent 1009 close frame - + 3696 Unwrap JavaxWebSocketClientContainer.connectToServer() exceptions - + 3698 Missing WebSocket ServerContainer after server restart - + 3700 stackoverflow in WebAppClassLoaderUrlStreamTest - + 3705 Review ClientUpgradeRequest exception handling - + 3708 Swap various java.lang.String replace() methods for better performant - ones - + 3712 change maxIdleTime to idleTimeout in jetty-10 websockets - + 3719 Clean up jetty-10 modules - + 3726 Remove OSGi export uses of servlet-api from jetty-util - + 3731 Add testing of CDI behaviors - + 3736 NPE from WebAppClassLoader during CDI - + 3746 ClassCastException in WriteFlusher.java - IdleState cannot be cast to - FailedState - + 3749 Memory leak while processing AsyncListener annotations - + 3751 Modern Configure DTD / FPI is used inconsistently - + 3755 ServerWithAnnotations doesn't do anything - + 3758 Avoid sending empty trailer frames for http/2 requests - + 3762 WebSocketConnectionStatsTest test uses port 8080 - + 3782 X-Forwarded-Port overrides X-Forwarded-For - + 3786 ALPN support for Java 14 - + 3789 XmlConfiguration set from property - + 3798 ClasspathPattern match method throws NPE. URI can be null - + 3799 Programmatically added listeners from - ServletContextListener.contextInitialzed() are not called - + 3804 Weld/CDI XML backwards compat? - + 3805 XmlConfiguration odd behavior for numbers - + 3809 sending WebSocket close frame with error StatusCode does not do a hard - close (Jetty-10) - + 3815 PropertyFileLoginModule adds user principle as a role - + 3835 WebSocketSession are not being stopped properly - + 3839 JavaxWebSocketServletContainerInitializer fails - + 3840 Byte-range request performance problems with large files - + 3849 ClosedChannelException from jetty-test-webapp javax websocket chat - example - jetty-9.4.28.v20200408 - 08 April 2020 + 847 Setting async timeout on WebSocketClient does not seem to timeout writes + 2896 Wrong Certificate Selected When Using Multiple Virtual Host Names in diff --git a/build-resources/pom.xml b/build-resources/pom.xml index 89f78175b64..6e6de0f7032 100644 --- a/build-resources/pom.xml +++ b/build-resources/pom.xml @@ -67,6 +67,7 @@ maven-deploy-plugin 2.8.2 + true diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java index 7811257c09f..bcf230ac427 100644 --- a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java +++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java @@ -91,7 +91,7 @@ public class AbstractRestServlet extends HttpServlet { try { - return ("http://open.api.ebay.com/shopping?MaxEntries=3&appid=" + _appid + + return ("https://open.api.ebay.com/shopping?MaxEntries=3&appid=" + _appid + "&version=573&siteid=0&callname=FindItems&responseencoding=JSON&QueryKeywords=" + URLEncoder.encode(item, "UTF-8")); } diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java index c89fda2c2bd..60ebe22aa2e 100644 --- a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java +++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.ajax.JSON; +import org.eclipse.jetty.util.ssl.SslContextFactory; /** * Servlet implementation class AsyncRESTServlet. diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java index f959e4bfb63..bdf2ec2efcf 100644 --- a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java +++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/OneWebAppTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.LifeCycle; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -54,8 +55,10 @@ public class OneWebAppTest extends AbstractEmbeddedTest } @Test + @Tag("external") public void testGetAsyncRest() throws Exception { + // The async rest webapp forwards the call to ebay.com. URI uri = server.getURI().resolve("/testAsync?items=mouse,beer,gnome"); ContentResponse response = client.newRequest(uri) .method(HttpMethod.GET) diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationIntrospector.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationIntrospector.java index 4ffe33c9cc2..b49d663a7c1 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationIntrospector.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationIntrospector.java @@ -28,6 +28,7 @@ import java.util.Set; import org.eclipse.jetty.servlet.BaseHolder; import org.eclipse.jetty.servlet.Source.Origin; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebDescriptor; import org.slf4j.Logger; @@ -41,6 +42,8 @@ import org.slf4j.LoggerFactory; public class AnnotationIntrospector { private static final Logger LOG = LoggerFactory.getLogger(AnnotationIntrospector.class); + + private final AutoLock _lock = new AutoLock(); private final Set> _introspectedClasses = new HashSet<>(); private final List _handlers = new ArrayList(); private final WebAppContext _context; @@ -195,14 +198,13 @@ public class AnnotationIntrospector Class clazz = o.getClass(); - synchronized (_introspectedClasses) + try (AutoLock l = _lock.lock()) { - //Synchronize on the set of already introspected classes. - //This ensures that only 1 thread can be introspecting, and that - //thread must have fully finished generating the products of - //introspection before another thread is allowed in. - //We remember the classes that we have introspected to avoid - //reprocessing the same class. + // Lock to ensure that only 1 thread can be introspecting, and that + // thread must have fully finished generating the products of + // the introspection before another thread is allowed in. + // We remember the classes that we have introspected to avoid + // reprocessing the same class. if (_introspectedClasses.add(clazz)) { for (IntrospectableAnnotationHandler handler : _handlers) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java index 8ffb9e703dc..8ace5cc2ae6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java @@ -18,74 +18,121 @@ package org.eclipse.jetty.client; +import java.io.IOException; +import java.util.ArrayDeque; import java.util.Collection; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.util.AtomicBiInteger; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Pool; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.thread.Sweeper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.util.stream.Collectors.toCollection; + @ManagedObject -public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable +public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable, Sweeper.Sweepable { private static final Logger LOG = LoggerFactory.getLogger(AbstractConnectionPool.class); - /** - * The connectionCount encodes both the total connections plus the pending connection counts, so both can be atomically changed. - * The bottom 32 bits represent the total connections and the top 32 bits represent the pending connections. - */ - private final AtomicBiInteger connections = new AtomicBiInteger(); - private final AtomicBoolean closed = new AtomicBoolean(); private final HttpDestination destination; - private final int maxConnections; private final Callback requester; + private final Pool pool; - protected AbstractConnectionPool(HttpDestination destination, int maxConnections, Callback requester) + protected AbstractConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester) { this.destination = destination; - this.maxConnections = maxConnections; this.requester = requester; + @SuppressWarnings("unchecked") + Pool pool = destination.getBean(Pool.class); + if (pool == null) + { + pool = new Pool<>(maxConnections, cache ? 1 : 0); + destination.addBean(pool); + } + this.pool = pool; } - protected HttpDestination getHttpDestination() + @Override + public CompletableFuture preCreateConnections(int connectionCount) { - return destination; + CompletableFuture[] futures = new CompletableFuture[connectionCount]; + for (int i = 0; i < connectionCount; i++) + { + futures[i] = tryCreateReturningFuture(pool.getMaxEntries()); + } + return CompletableFuture.allOf(futures); + } + + protected int getMaxMultiplex() + { + return pool.getMaxMultiplex(); + } + + protected void setMaxMultiplex(int maxMultiplex) + { + pool.setMaxMultiplex(maxMultiplex); + } + + protected int getMaxUsageCount() + { + return pool.getMaxUsageCount(); + } + + protected void setMaxUsageCount(int maxUsageCount) + { + pool.setMaxUsageCount(maxUsageCount); + } + + @ManagedAttribute(value = "The number of active connections", readonly = true) + public int getActiveConnectionCount() + { + return pool.getInUseCount(); + } + + @ManagedAttribute(value = "The number of idle connections", readonly = true) + public int getIdleConnectionCount() + { + return pool.getIdleCount(); } @ManagedAttribute(value = "The max number of connections", readonly = true) public int getMaxConnectionCount() { - return maxConnections; + return pool.getMaxEntries(); } @ManagedAttribute(value = "The number of connections", readonly = true) public int getConnectionCount() { - return connections.getLo(); + return pool.size(); } @ManagedAttribute(value = "The number of pending connections", readonly = true) public int getPendingConnectionCount() { - return connections.getHi(); + return pool.getReservedCount(); } @Override public boolean isEmpty() { - return connections.getLo() == 0; + return pool.size() == 0; } @Override public boolean isClosed() { - return closed.get(); + return pool.isClosed(); } @Override @@ -112,101 +159,175 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable */ protected void tryCreate(int maxPending) { - while (true) + tryCreateReturningFuture(maxPending); + } + + private CompletableFuture tryCreateReturningFuture(int maxPending) + { + if (LOG.isDebugEnabled()) { - long encoded = connections.get(); - int pending = AtomicBiInteger.getHi(encoded); - int total = AtomicBiInteger.getLo(encoded); + LOG.debug("tryCreate {}/{} connections {}/{} pending", pool.size(), pool.getMaxEntries(), getPendingConnectionCount(), maxPending); + } - if (LOG.isDebugEnabled()) - LOG.debug("tryCreate {}/{} connections {}/{} pending", total, maxConnections, pending, maxPending); + Pool.Entry entry = pool.reserve(maxPending); + if (entry == null) + return CompletableFuture.completedFuture(null); - if (total >= maxConnections) - return; + if (LOG.isDebugEnabled()) + LOG.debug("newConnection {}/{} connections {}/{} pending", pool.size(), pool.getMaxEntries(), getPendingConnectionCount(), maxPending); - if (maxPending >= 0 && pending >= maxPending) - return; - - if (connections.compareAndSet(encoded, pending + 1, total + 1)) + CompletableFuture future = new CompletableFuture<>(); + destination.newConnection(new Promise<>() + { + @Override + public void succeeded(Connection connection) { if (LOG.isDebugEnabled()) - LOG.debug("newConnection {}/{} connections {}/{} pending", total + 1, maxConnections, pending + 1, maxPending); - - destination.newConnection(new Promise<>() + LOG.debug("Connection {}/{} creation succeeded {}", pool.size(), pool.getMaxEntries(), connection); + if (!(connection instanceof Attachable)) { - @Override - public void succeeded(Connection connection) - { - if (LOG.isDebugEnabled()) - LOG.debug("Connection {}/{} creation succeeded {}", total + 1, maxConnections, connection); - connections.add(-1, 0); - onCreated(connection); - proceed(); - } - - @Override - public void failed(Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Connection " + (total + 1) + "/" + maxConnections + " creation failed", x); - connections.add(-1, -1); - requester.failed(x); - } - }); - - return; + failed(new IllegalArgumentException("Invalid connection object: " + connection)); + return; + } + ((Attachable)connection).setAttachment(entry); + onCreated(connection); + entry.enable(connection, false); + idle(connection, false); + future.complete(null); + proceed(); } - } + + @Override + public void failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Connection " + pool.size() + "/" + pool.getMaxEntries() + " creation failed", x); + entry.remove(); + future.completeExceptionally(x); + requester.failed(x); + } + }); + + return future; } @Override public boolean accept(Connection connection) { - while (true) - { - int count = connections.getLo(); - if (count >= maxConnections) - return false; - if (connections.compareAndSetLo(count, count + 1)) - return true; - } + if (!(connection instanceof Attachable)) + throw new IllegalArgumentException("Invalid connection object: " + connection); + Pool.Entry entry = pool.reserve(-1); + if (entry == null) + return false; + if (LOG.isDebugEnabled()) + LOG.debug("onCreating {} {}", entry, connection); + Attachable attachable = (Attachable)connection; + attachable.setAttachment(entry); + onCreated(connection); + entry.enable(connection, false); + idle(connection, false); + return true; } - protected abstract void onCreated(Connection connection); - protected void proceed() { requester.succeeded(); } - protected abstract Connection activate(); - - protected Connection active(Connection connection) + protected Connection activate() { - if (LOG.isDebugEnabled()) - LOG.debug("Connection active {}", connection); - acquired(connection); - return connection; + Pool.Entry entry = pool.acquire(); + if (entry != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("activated {}", entry); + Connection connection = entry.getPooled(); + acquired(connection); + return connection; + } + return null; } - protected void acquired(Connection connection) + @Override + public boolean isActive(Connection connection) + { + if (!(connection instanceof Attachable)) + throw new IllegalArgumentException("Invalid connection object: " + connection); + Attachable attachable = (Attachable)connection; + @SuppressWarnings("unchecked") + Pool.Entry entry = (Pool.Entry)attachable.getAttachment(); + if (entry == null) + return false; + if (LOG.isDebugEnabled()) + LOG.debug("isActive {}", entry); + return !entry.isIdle(); + } + + @Override + public boolean release(Connection connection) + { + if (!deactivate(connection)) + return false; + released(connection); + return idle(connection, isClosed()); + } + + protected boolean deactivate(Connection connection) + { + if (!(connection instanceof Attachable)) + throw new IllegalArgumentException("Invalid connection object: " + connection); + Attachable attachable = (Attachable)connection; + @SuppressWarnings("unchecked") + Pool.Entry entry = (Pool.Entry)attachable.getAttachment(); + if (entry == null) + return true; + boolean reusable = pool.release(entry); + if (LOG.isDebugEnabled()) + LOG.debug("Released ({}) {}", reusable, entry); + if (reusable) + return true; + remove(connection); + return false; + } + + @Override + public boolean remove(Connection connection) + { + return remove(connection, false); + } + + protected boolean remove(Connection connection, boolean force) + { + if (!(connection instanceof Attachable)) + throw new IllegalArgumentException("Invalid connection object: " + connection); + Attachable attachable = (Attachable)connection; + @SuppressWarnings("unchecked") + Pool.Entry entry = (Pool.Entry)attachable.getAttachment(); + if (entry == null) + return false; + attachable.setAttachment(null); + boolean removed = pool.remove(entry); + if (LOG.isDebugEnabled()) + LOG.debug("Removed ({}) {}", removed, entry); + if (removed || force) + { + released(connection); + removed(connection); + } + return removed; + } + + protected void onCreated(Connection connection) { } protected boolean idle(Connection connection, boolean close) { - if (close) - { - if (LOG.isDebugEnabled()) - LOG.debug("Connection idle close {}", connection); - return false; - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Connection idle {}", connection); - return true; - } + return !close; + } + + protected void acquired(Connection connection) + { } protected void released(Connection connection) @@ -215,28 +336,68 @@ public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable protected void removed(Connection connection) { - int pooled = connections.addAndGetLo(-1); - if (LOG.isDebugEnabled()) - LOG.debug("Connection removed {} - pooled: {}", connection, pooled); + } + + Queue getIdleConnections() + { + return pool.values().stream() + .filter(Pool.Entry::isIdle) + .filter(entry -> !entry.isClosed()) + .map(Pool.Entry::getPooled) + .collect(toCollection(ArrayDeque::new)); + } + + Collection getActiveConnections() + { + return pool.values().stream() + .filter(entry -> !entry.isIdle()) + .filter(entry -> !entry.isClosed()) + .map(Pool.Entry::getPooled) + .collect(Collectors.toList()); } @Override public void close() { - if (closed.compareAndSet(false, true)) - { - connections.set(0, 0); - } - } - - protected void close(Collection connections) - { - connections.forEach(Connection::close); + pool.close(); } @Override - public String dump() + public void dump(Appendable out, String indent) throws IOException { - return Dumpable.dump(this); + Dumpable.dumpObjects(out, indent, this); + } + + @Override + public boolean sweep() + { + pool.values().stream().filter(entry -> entry.getPooled() instanceof Sweeper.Sweepable).forEach(entry -> + { + Connection connection = entry.getPooled(); + if (((Sweeper.Sweepable)connection).sweep()) + { + boolean removed = remove(connection); + LOG.warn("Connection swept: {}{}{} from active connections{}{}", + connection, + System.lineSeparator(), + removed ? "Removed" : "Not removed", + System.lineSeparator(), + dump()); + } + }); + return false; + } + + @Override + public String toString() + { + return String.format("%s@%x[c=%d/%d/%d,a=%d,i=%d]", + getClass().getSimpleName(), + hashCode(), + getPendingConnectionCount(), + getConnectionCount(), + getMaxConnectionCount(), + getActiveConnectionCount(), + getIdleConnectionCount()); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java index a2ad7c14263..7b79441de0d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.client; import java.io.Closeable; +import java.util.concurrent.CompletableFuture; import org.eclipse.jetty.client.api.Connection; @@ -27,6 +28,16 @@ import org.eclipse.jetty.client.api.Connection; */ public interface ConnectionPool extends Closeable { + /** + * Optionally pre-create up to connectionCount + * connections so they are immediately ready for use. + * @param connectionCount the number of connections to pre-start. + */ + default CompletableFuture preCreateConnections(int connectionCount) + { + return CompletableFuture.completedFuture(null); + } + /** * @param connection the connection to test * @return whether the given connection is currently in use diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java index e9415af3cd5..650a337e6c2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java @@ -18,303 +18,33 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; - -import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.util.component.DumpableCollection; -import org.eclipse.jetty.util.thread.Sweeper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @ManagedObject -public class DuplexConnectionPool extends AbstractConnectionPool implements Sweeper.Sweepable +public class DuplexConnectionPool extends AbstractConnectionPool { - private static final Logger LOG = LoggerFactory.getLogger(DuplexConnectionPool.class); - - private final ReentrantLock lock = new ReentrantLock(); - private final Deque idleConnections; - private final Set activeConnections; - public DuplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester) { - super(destination, maxConnections, requester); - this.idleConnections = new ArrayDeque<>(maxConnections); - this.activeConnections = new HashSet<>(maxConnections); + this(destination, maxConnections, true, requester); } - protected void lock() + public DuplexConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester) { - lock.lock(); - } - - protected void unlock() - { - lock.unlock(); - } - - @ManagedAttribute(value = "The number of idle connections", readonly = true) - public int getIdleConnectionCount() - { - lock(); - try - { - return idleConnections.size(); - } - finally - { - unlock(); - } - } - - @ManagedAttribute(value = "The number of active connections", readonly = true) - public int getActiveConnectionCount() - { - lock(); - try - { - return activeConnections.size(); - } - finally - { - unlock(); - } - } - - public Queue getIdleConnections() - { - return idleConnections; - } - - public Collection getActiveConnections() - { - return activeConnections; + super(destination, maxConnections, cache, requester); } @Override - public boolean isActive(Connection connection) + @ManagedAttribute(value = "The maximum amount of times a connection is used before it gets closed") + public int getMaxUsageCount() { - lock(); - try - { - return activeConnections.contains(connection); - } - finally - { - unlock(); - } + return super.getMaxUsageCount(); } @Override - protected void onCreated(Connection connection) + public void setMaxUsageCount(int maxUsageCount) { - lock(); - try - { - // Use "cold" new connections as last. - idleConnections.offer(connection); - } - finally - { - unlock(); - } - - idle(connection, false); - } - - @Override - protected Connection activate() - { - Connection connection; - lock(); - try - { - connection = idleConnections.poll(); - if (connection == null) - return null; - activeConnections.add(connection); - } - finally - { - unlock(); - } - - return active(connection); - } - - @Override - public boolean release(Connection connection) - { - boolean closed = isClosed(); - lock(); - try - { - if (!activeConnections.remove(connection)) - return false; - - if (!closed) - { - // Make sure we use "hot" connections first. - deactivate(connection); - } - } - finally - { - unlock(); - } - - released(connection); - return idle(connection, closed); - } - - protected boolean deactivate(Connection connection) - { - return idleConnections.offerFirst(connection); - } - - @Override - public boolean remove(Connection connection) - { - return remove(connection, false); - } - - protected boolean remove(Connection connection, boolean force) - { - boolean activeRemoved; - boolean idleRemoved; - lock(); - try - { - activeRemoved = activeConnections.remove(connection); - idleRemoved = idleConnections.remove(connection); - } - finally - { - unlock(); - } - - if (activeRemoved || force) - released(connection); - boolean removed = activeRemoved || idleRemoved || force; - if (removed) - removed(connection); - return removed; - } - - @Override - public void close() - { - super.close(); - - List connections = new ArrayList<>(); - lock(); - try - { - connections.addAll(idleConnections); - idleConnections.clear(); - connections.addAll(activeConnections); - activeConnections.clear(); - } - finally - { - unlock(); - } - - close(connections); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - DumpableCollection active; - DumpableCollection idle; - lock(); - try - { - active = new DumpableCollection("active", new ArrayList<>(activeConnections)); - idle = new DumpableCollection("idle", new ArrayList<>(idleConnections)); - } - finally - { - unlock(); - } - dump(out, indent, active, idle); - } - - protected void dump(Appendable out, String indent, Object... items) throws IOException - { - Dumpable.dumpObjects(out, indent, this, items); - } - - @Override - public boolean sweep() - { - List toSweep; - lock(); - try - { - toSweep = activeConnections.stream() - .filter(connection -> connection instanceof Sweeper.Sweepable) - .collect(Collectors.toList()); - } - finally - { - unlock(); - } - - for (Connection connection : toSweep) - { - if (((Sweeper.Sweepable)connection).sweep()) - { - boolean removed = remove(connection, true); - LOG.warn("Connection swept: {}{}{} from active connections{}{}", - connection, - System.lineSeparator(), - removed ? "Removed" : "Not removed", - System.lineSeparator(), - dump()); - } - } - - return false; - } - - @Override - public String toString() - { - int activeSize; - int idleSize; - lock(); - try - { - activeSize = activeConnections.size(); - idleSize = idleConnections.size(); - } - finally - { - unlock(); - } - - return String.format("%s@%x[c=%d/%d/%d,a=%d,i=%d]", - getClass().getSimpleName(), - hashCode(), - getPendingConnectionCount(), - getConnectionCount(), - getMaxConnectionCount(), - activeSize, - idleSize); + super.setMaxUsageCount(maxUsageCount); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java index 1867615b3ef..99485134601 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java @@ -19,13 +19,15 @@ package org.eclipse.jetty.client; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class HttpChannel { - protected static final Logger LOG = LoggerFactory.getLogger(HttpChannel.class); + private static final Logger LOG = LoggerFactory.getLogger(HttpChannel.class); + private final AutoLock _lock = new AutoLock(); private final HttpDestination _destination; private final TimeoutCompleteListener _totalTimeout; private HttpExchange _exchange; @@ -58,7 +60,7 @@ public abstract class HttpChannel { boolean result = false; boolean abort = true; - synchronized (this) + try (AutoLock l = _lock.lock()) { if (_exchange == null) { @@ -85,7 +87,7 @@ public abstract class HttpChannel public boolean disassociate(HttpExchange exchange) { boolean result = false; - synchronized (this) + try (AutoLock l = _lock.lock()) { HttpExchange existing = _exchange; _exchange = null; @@ -103,7 +105,7 @@ public abstract class HttpChannel public HttpExchange getHttpExchange() { - synchronized (this) + try (AutoLock l = _lock.lock()) { return _exchange; } 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 4f4b40a60fd..34e434c3827 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 @@ -647,7 +647,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @return the max time, in milliseconds, a connection can take to connect to destinations + * @return the max time, in milliseconds, a connection can take to connect to destinations. Zero value means infinite timeout. */ @ManagedAttribute("The timeout, in milliseconds, for connect() operations") public long getConnectTimeout() @@ -656,7 +656,7 @@ public class HttpClient extends ContainerLifeCycle } /** - * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations + * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations. Zero value means infinite timeout. * @see java.net.Socket#connect(SocketAddress, int) */ public void setConnectTimeout(long connectTimeout) @@ -1111,10 +1111,14 @@ public class HttpClient extends ContainerLifeCycle return encodingField; } + /** + * @param host the host to normalize + * @return the host itself + * @deprecated no replacement, do not use it + */ + @Deprecated protected String normalizeHost(String host) { - if (host != null && host.startsWith("[") && host.endsWith("]")) - return host.substring(1, host.length() - 1); return host; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 39fde1c7e08..b70044f983a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -35,15 +35,19 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.HttpCookieStore; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class HttpConnection implements IConnection +public abstract class HttpConnection implements IConnection, Attachable { private static final Logger LOG = LoggerFactory.getLogger(HttpConnection.class); + private final AutoLock lock = new AutoLock(); private final HttpDestination destination; + private Object attachment; private int idleTimeoutGuard; private long idleTimeoutStamp; @@ -87,7 +91,7 @@ public abstract class HttpConnection implements IConnection // the request is associated to the channel and sent. // Use a counter to support multiplexed requests. boolean send; - synchronized (this) + try (AutoLock l = lock.lock()) { send = idleTimeoutGuard >= 0; if (send) @@ -111,7 +115,7 @@ public abstract class HttpConnection implements IConnection result = new SendFailure(new HttpRequestException("Could not associate request to connection", request), false); } - synchronized (this) + try (AutoLock l = lock.lock()) { --idleTimeoutGuard; idleTimeoutStamp = System.nanoTime(); @@ -250,7 +254,7 @@ public abstract class HttpConnection implements IConnection public boolean onIdleTimeout(long idleTimeout) { - synchronized (this) + try (AutoLock l = lock.lock()) { if (idleTimeoutGuard == 0) { @@ -271,6 +275,18 @@ public abstract class HttpConnection implements IConnection } } + @Override + public void setAttachment(Object obj) + { + this.attachment = obj; + } + + @Override + public Object getAttachment() + { + return attachment; + } + @Override public String toString() { 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 a93ed908a2c..175f397075d 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 @@ -427,6 +427,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest { if (connectionPool.isActive(connection)) { + // trigger the next request after releasing the connection if (connectionPool.release(connection)) send(false); else 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 75a89dcefe4..d79eed6f253 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 @@ -23,6 +23,7 @@ import java.util.List; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +31,7 @@ public class HttpExchange { private static final Logger LOG = LoggerFactory.getLogger(HttpExchange.class); + private final AutoLock lock = new AutoLock(); private final HttpDestination destination; private final HttpRequest request; private final List listeners; @@ -68,7 +70,7 @@ public class HttpExchange public Throwable getRequestFailure() { - synchronized (this) + try (AutoLock l = lock.lock()) { return requestFailure; } @@ -86,7 +88,7 @@ public class HttpExchange public Throwable getResponseFailure() { - synchronized (this) + try (AutoLock l = lock.lock()) { return responseFailure; } @@ -103,7 +105,7 @@ public class HttpExchange { boolean result = false; boolean abort = false; - synchronized (this) + try (AutoLock l = lock.lock()) { // Only associate if the exchange state is initial, // as the exchange could be already failed. @@ -127,7 +129,7 @@ public class HttpExchange void disassociate(HttpChannel channel) { boolean abort = false; - synchronized (this) + try (AutoLock l = lock.lock()) { if (_channel != channel || requestState != State.TERMINATED || responseState != State.TERMINATED) abort = true; @@ -140,7 +142,7 @@ public class HttpExchange private HttpChannel getHttpChannel() { - synchronized (this) + try (AutoLock l = lock.lock()) { return _channel; } @@ -148,7 +150,7 @@ public class HttpExchange public boolean requestComplete(Throwable failure) { - synchronized (this) + try (AutoLock l = lock.lock()) { return completeRequest(failure); } @@ -167,7 +169,7 @@ public class HttpExchange public boolean responseComplete(Throwable failure) { - synchronized (this) + try (AutoLock l = lock.lock()) { return completeResponse(failure); } @@ -187,7 +189,7 @@ public class HttpExchange public Result terminateRequest() { Result result = null; - synchronized (this) + try (AutoLock l = lock.lock()) { if (requestState == State.COMPLETED) requestState = State.TERMINATED; @@ -204,7 +206,7 @@ public class HttpExchange public Result terminateResponse() { Result result = null; - synchronized (this) + try (AutoLock l = lock.lock()) { if (responseState == State.COMPLETED) responseState = State.TERMINATED; @@ -224,7 +226,7 @@ public class HttpExchange // This will avoid that this exchange can be associated to a channel. boolean abortRequest; boolean abortResponse; - synchronized (this) + try (AutoLock l = lock.lock()) { abortRequest = completeRequest(failure); abortResponse = completeResponse(failure); @@ -283,7 +285,7 @@ public class HttpExchange public void resetResponse() { - synchronized (this) + try (AutoLock l = lock.lock()) { responseState = State.PENDING; responseFailure = null; @@ -300,7 +302,7 @@ public class HttpExchange @Override public String toString() { - synchronized (this) + try (AutoLock l = lock.lock()) { return String.format("%s@%x req=%s/%s@%h res=%s/%s@%h", HttpExchange.class.getSimpleName(), diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java index 82b1918428d..b2863fad1c2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; @@ -276,11 +277,12 @@ public class HttpProxy extends ProxyConfiguration.Proxy } } - private static class ProxyConnection implements Connection + private static class ProxyConnection implements Connection, Attachable { private final Destination destination; private final Connection connection; private final Promise promise; + private Object attachment; private ProxyConnection(Destination destination, Connection connection, Promise promise) { @@ -313,6 +315,18 @@ public class HttpProxy extends ProxyConfiguration.Proxy { return connection.isClosed(); } + + @Override + public void setAttachment(Object obj) + { + this.attachment = obj; + } + + @Override + public Object getAttachment() + { + return attachment; + } } private static class TunnelPromise implements Promise diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index 59322a3f44e..2466f6ed59b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -42,6 +42,7 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.MathUtils; import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +75,7 @@ public abstract class HttpReceiver { private static final Logger LOG = LoggerFactory.getLogger(HttpReceiver.class); + private final AutoLock lock = new AutoLock(); private final AtomicReference responseState = new AtomicReference<>(ResponseState.IDLE); private final HttpChannel channel; private ContentListeners contentListeners; @@ -98,7 +100,7 @@ public abstract class HttpReceiver throw new IllegalArgumentException("Invalid demand " + n); boolean resume = false; - synchronized (this) + try (AutoLock l = lock.lock()) { demand = MathUtils.cappedAdd(demand, n); if (stalled) @@ -126,7 +128,7 @@ public abstract class HttpReceiver private long demand(LongUnaryOperator operator) { - synchronized (this) + try (AutoLock l = lock.lock()) { return demand = operator.applyAsLong(demand); } @@ -134,7 +136,7 @@ public abstract class HttpReceiver protected boolean hasDemandOrStall() { - synchronized (this) + try (AutoLock l = lock.lock()) { stalled = demand <= 0; return !stalled; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index dc3a9219714..babe91ef776 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -99,7 +99,7 @@ public class HttpRequest implements Request this.client = client; this.conversation = conversation; scheme = uri.getScheme(); - host = client.normalizeHost(uri.getHost()); + host = uri.getHost(); port = HttpClient.normalizePort(scheme, uri.getPort()); path = uri.getRawPath(); query = uri.getRawQuery(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java index 3fcec061c34..4c1c3ef314b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java @@ -39,7 +39,7 @@ public class LeakTrackingConnectionPool extends DuplexConnectionPool public LeakTrackingConnectionPool(HttpDestination destination, int maxConnections, Callback requester) { - super(destination, maxConnections, requester); + super((HttpDestination)destination, maxConnections, requester); start(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java index 3af05ee8e19..09b1cb01f83 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java @@ -18,307 +18,47 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.util.component.DumpableCollection; -import org.eclipse.jetty.util.thread.Sweeper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; -public class MultiplexConnectionPool extends AbstractConnectionPool implements ConnectionPool.Multiplexable, Sweeper.Sweepable +@ManagedObject +public class MultiplexConnectionPool extends AbstractConnectionPool implements ConnectionPool.Multiplexable { - private static final Logger LOG = LoggerFactory.getLogger(MultiplexConnectionPool.class); - - private final Deque idleConnections; - private final Map activeConnections; - private int maxMultiplex; - public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex) { - super(destination, maxConnections, requester); - this.idleConnections = new ArrayDeque<>(maxConnections); - this.activeConnections = new LinkedHashMap<>(maxConnections); - this.maxMultiplex = maxMultiplex; - } - - @Override - public Connection acquire(boolean create) - { - Connection connection = activate(); - if (connection == null && create) - { - int queuedRequests = getHttpDestination().getQueuedRequestCount(); - int maxMultiplex = getMaxMultiplex(); - int maxPending = ceilDiv(queuedRequests, maxMultiplex); - tryCreate(maxPending); - connection = activate(); - } - return connection; - } - - /** - * @param a the dividend - * @param b the divisor - * @return the ceiling of the algebraic quotient - */ - private static int ceilDiv(int a, int b) - { - return (a + b - 1) / b; + this(destination, maxConnections, true, requester, maxMultiplex); + } + + public MultiplexConnectionPool(HttpDestination destination, int maxConnections, boolean cache, Callback requester, int maxMultiplex) + { + super(destination, maxConnections, cache, requester); + setMaxMultiplex(maxMultiplex); } @Override + @ManagedAttribute(value = "The multiplexing factor of connections") public int getMaxMultiplex() { - synchronized (this) - { - return maxMultiplex; - } + return super.getMaxMultiplex(); } @Override public void setMaxMultiplex(int maxMultiplex) { - synchronized (this) - { - this.maxMultiplex = maxMultiplex; - } + super.setMaxMultiplex(maxMultiplex); } @Override - public boolean accept(Connection connection) + @ManagedAttribute(value = "The maximum amount of times a connection is used before it gets closed") + public int getMaxUsageCount() { - boolean accepted = super.accept(connection); - if (LOG.isDebugEnabled()) - LOG.debug("Accepted {} {}", accepted, connection); - if (accepted) - { - synchronized (this) - { - Holder holder = new Holder(connection); - activeConnections.put(connection, holder); - ++holder.count; - } - active(connection); - } - return accepted; + return super.getMaxUsageCount(); } @Override - public boolean isActive(Connection connection) + public void setMaxUsageCount(int maxUsageCount) { - synchronized (this) - { - return activeConnections.containsKey(connection); - } - } - - @Override - protected void onCreated(Connection connection) - { - synchronized (this) - { - // Use "cold" connections as last. - idleConnections.offer(new Holder(connection)); - } - idle(connection, false); - } - - @Override - protected Connection activate() - { - Holder result = null; - synchronized (this) - { - for (Holder holder : activeConnections.values()) - { - if (holder.count < maxMultiplex) - { - result = holder; - break; - } - } - - if (result == null) - { - Holder holder = idleConnections.poll(); - if (holder == null) - return null; - activeConnections.put(holder.connection, holder); - result = holder; - } - - ++result.count; - } - return active(result.connection); - } - - @Override - public boolean release(Connection connection) - { - boolean closed = isClosed(); - boolean idle = false; - Holder holder; - synchronized (this) - { - holder = activeConnections.get(connection); - if (holder != null) - { - int count = --holder.count; - if (count == 0) - { - activeConnections.remove(connection); - if (!closed) - { - idleConnections.offerFirst(holder); - idle = true; - } - } - } - } - if (holder == null) - return false; - - released(connection); - if (idle || closed) - return idle(connection, closed); - return true; - } - - @Override - public boolean remove(Connection connection) - { - return remove(connection, false); - } - - protected boolean remove(Connection connection, boolean force) - { - boolean activeRemoved = true; - boolean idleRemoved = false; - synchronized (this) - { - Holder holder = activeConnections.remove(connection); - if (holder == null) - { - activeRemoved = false; - for (Iterator iterator = idleConnections.iterator(); iterator.hasNext(); ) - { - holder = iterator.next(); - if (holder.connection == connection) - { - idleRemoved = true; - iterator.remove(); - break; - } - } - } - } - if (activeRemoved || force) - released(connection); - boolean removed = activeRemoved || idleRemoved || force; - if (removed) - removed(connection); - return removed; - } - - @Override - public void close() - { - super.close(); - List connections; - synchronized (this) - { - connections = idleConnections.stream().map(holder -> holder.connection).collect(Collectors.toList()); - connections.addAll(activeConnections.keySet()); - } - close(connections); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - DumpableCollection active; - DumpableCollection idle; - synchronized (this) - { - active = new DumpableCollection("active", new ArrayList<>(activeConnections.values())); - idle = new DumpableCollection("idle", new ArrayList<>(idleConnections)); - } - Dumpable.dumpObjects(out, indent, this, active, idle); - } - - @Override - public boolean sweep() - { - List toSweep = new ArrayList<>(); - synchronized (this) - { - activeConnections.values().stream() - .map(holder -> holder.connection) - .filter(connection -> connection instanceof Sweeper.Sweepable) - .collect(Collectors.toCollection(() -> toSweep)); - } - for (Connection connection : toSweep) - { - if (((Sweeper.Sweepable)connection).sweep()) - { - boolean removed = remove(connection, true); - LOG.warn("Connection swept: {}{}{} from active connections{}{}", - connection, - System.lineSeparator(), - removed ? "Removed" : "Not removed", - System.lineSeparator(), - dump()); - } - } - return false; - } - - @Override - public String toString() - { - int activeSize; - int idleSize; - synchronized (this) - { - activeSize = activeConnections.size(); - idleSize = idleConnections.size(); - } - return String.format("%s@%x[connections=%d/%d/%d,multiplex=%d,active=%d,idle=%d]", - getClass().getSimpleName(), - hashCode(), - getPendingConnectionCount(), - getConnectionCount(), - getMaxConnectionCount(), - getMaxMultiplex(), - activeSize, - idleSize); - } - - private static class Holder - { - private final Connection connection; - private int count; - - private Holder(Connection connection) - { - this.connection = connection; - } - - @Override - public String toString() - { - return String.format("%s[%d]", connection, count); - } + super.setMaxUsageCount(maxUsageCount); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java index 6107dc8d38a..c78a1c7b9ab 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java @@ -25,6 +25,7 @@ import java.util.Objects; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.URIUtil; /** @@ -147,7 +148,7 @@ public class Origin public Address(String host, int port) { - this.host = Objects.requireNonNull(host); + this.host = HostPort.normalizeHost(Objects.requireNonNull(host)); this.port = port; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RoundRobinConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RoundRobinConnectionPool.java index e13fba9390d..825598b484e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RoundRobinConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RoundRobinConnectionPool.java @@ -18,21 +18,22 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Pool; import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.Dumpable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @ManagedObject -public class RoundRobinConnectionPool extends AbstractConnectionPool implements ConnectionPool.Multiplexable +public class RoundRobinConnectionPool extends MultiplexConnectionPool { - private final List entries; - private int maxMultiplex; - private int index; + private static final Logger LOG = LoggerFactory.getLogger(RoundRobinConnectionPool.class); + + private final AtomicInteger offset = new AtomicInteger(); + private final Pool pool; public RoundRobinConnectionPool(HttpDestination destination, int maxConnections, Callback requester) { @@ -41,220 +42,31 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool implements public RoundRobinConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplex) { - super(destination, maxConnections, requester); - entries = new ArrayList<>(maxConnections); - for (int i = 0; i < maxConnections; ++i) - { - entries.add(new Entry()); - } - this.maxMultiplex = maxMultiplex; - } - - @Override - public int getMaxMultiplex() - { - synchronized (this) - { - return maxMultiplex; - } - } - - @Override - public void setMaxMultiplex(int maxMultiplex) - { - synchronized (this) - { - this.maxMultiplex = maxMultiplex; - } - } - - /** - *

Returns an idle connection, if available, following a round robin algorithm; - * otherwise it always tries to create a new connection, up until the max connection count.

- * - * @param create this parameter is ignored and assumed to be always {@code true} - * @return an idle connection or {@code null} if no idle connections are available - */ - @Override - public Connection acquire(boolean create) - { - // The nature of this connection pool is such that a - // connection must always be present in the next slot. - return super.acquire(true); - } - - @Override - protected void onCreated(Connection connection) - { - synchronized (this) - { - for (Entry entry : entries) - { - if (entry.connection == null) - { - entry.connection = connection; - break; - } - } - } - idle(connection, false); + super(destination, maxConnections, false, requester, maxMultiplex); + pool = destination.getBean(Pool.class); } @Override protected Connection activate() { - Connection connection = null; - synchronized (this) - { - int offset = 0; - int capacity = getMaxConnectionCount(); - while (offset < capacity) - { - int idx = index + offset; - if (idx >= capacity) - idx -= capacity; - - Entry entry = entries.get(idx); - - if (entry.connection == null) - break; - - if (entry.active < getMaxMultiplex()) - { - ++entry.active; - ++entry.used; - connection = entry.connection; - index += offset + 1; - if (index >= capacity) - index -= capacity; - break; - } - - ++offset; - } - } - return connection == null ? null : active(connection); + int offset = this.offset.get(); + Connection connection = activate(offset); + if (connection != null) + this.offset.getAndIncrement(); + return connection; } - @Override - public boolean isActive(Connection connection) + private Connection activate(int offset) { - synchronized (this) + Pool.Entry entry = pool.acquireAt(Math.abs(offset % pool.getMaxEntries())); + if (LOG.isDebugEnabled()) + LOG.debug("activated '{}'", entry); + if (entry != null) { - for (Entry entry : entries) - { - if (entry.connection == connection) - return entry.active > 0; - } - return false; - } - } - - @Override - public boolean release(Connection connection) - { - boolean found = false; - boolean idle = false; - synchronized (this) - { - for (Entry entry : entries) - { - if (entry.connection == connection) - { - found = true; - int active = --entry.active; - idle = active == 0; - break; - } - } - } - if (!found) - return false; - released(connection); - if (idle) - return idle(connection, isClosed()); - return true; - } - - @Override - public boolean remove(Connection connection) - { - boolean found = false; - synchronized (this) - { - for (Entry entry : entries) - { - if (entry.connection == connection) - { - found = true; - entry.reset(); - break; - } - } - } - if (found) - { - released(connection); - removed(connection); - } - return found; - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - List connections; - synchronized (this) - { - connections = new ArrayList<>(entries); - } - Dumpable.dumpObjects(out, indent, out, connections); - } - - @Override - public String toString() - { - int present = 0; - int active = 0; - synchronized (this) - { - for (Entry entry : entries) - { - if (entry.connection != null) - { - ++present; - if (entry.active > 0) - ++active; - } - } - } - return String.format("%s@%x[c=%d/%d/%d,a=%d]", - getClass().getSimpleName(), - hashCode(), - getPendingConnectionCount(), - present, - getMaxConnectionCount(), - active - ); - } - - private static class Entry - { - private Connection connection; - private int active; - private long used; - - private void reset() - { - connection = null; - active = 0; - used = 0; - } - - @Override - public String toString() - { - return String.format("{u=%d,c=%s}", used, connection); + Connection connection = entry.getPooled(); + acquired(connection); + return connection; } + return null; } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java index 8007131c1b9..4ea8de50030 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java @@ -19,15 +19,15 @@ package org.eclipse.jetty.client; import java.io.IOException; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; import org.eclipse.jetty.util.thread.Scheduler; import org.slf4j.Logger; @@ -66,10 +66,10 @@ public class ValidatingConnectionPool extends DuplexConnectionPool public ValidatingConnectionPool(HttpDestination destination, int maxConnections, Callback requester, Scheduler scheduler, long timeout) { - super(destination, maxConnections, requester); + super((HttpDestination)destination, maxConnections, requester); this.scheduler = scheduler; this.timeout = timeout; - this.quarantine = new HashMap<>(maxConnections); + this.quarantine = new ConcurrentHashMap<>(maxConnections); } @ManagedAttribute(value = "The number of validating connections", readonly = true) @@ -81,21 +81,11 @@ public class ValidatingConnectionPool extends DuplexConnectionPool @Override public boolean release(Connection connection) { - lock(); - try - { - if (!getActiveConnections().remove(connection)) - return false; - Holder holder = new Holder(connection); - holder.task = scheduler.schedule(holder, timeout, TimeUnit.MILLISECONDS); - quarantine.put(connection, holder); - if (LOG.isDebugEnabled()) - LOG.debug("Validating for {}ms {}", timeout, connection); - } - finally - { - unlock(); - } + Holder holder = new Holder(connection); + holder.task = scheduler.schedule(holder, timeout, TimeUnit.MILLISECONDS); + quarantine.put(connection, holder); + if (LOG.isDebugEnabled()) + LOG.debug("Validating for {}ms {}", timeout, connection); released(connection); return true; @@ -104,16 +94,7 @@ public class ValidatingConnectionPool extends DuplexConnectionPool @Override public boolean remove(Connection connection) { - Holder holder; - lock(); - try - { - holder = quarantine.remove(connection); - } - finally - { - unlock(); - } + Holder holder = quarantine.remove(connection); if (holder == null) return super.remove(connection); @@ -129,25 +110,16 @@ public class ValidatingConnectionPool extends DuplexConnectionPool } @Override - protected void dump(Appendable out, String indent, Object... items) throws IOException + public void dump(Appendable out, String indent) throws IOException { DumpableCollection toDump = new DumpableCollection("quarantine", quarantine.values()); - super.dump(out, indent, Stream.concat(Stream.of(items), Stream.of(toDump))); + Dumpable.dumpObjects(out, indent, this, toDump); } @Override public String toString() { - int size; - lock(); - try - { - size = quarantine.size(); - } - finally - { - unlock(); - } + int size = quarantine.size(); return String.format("%s[v=%d]", super.toString(), size); } @@ -169,20 +141,11 @@ public class ValidatingConnectionPool extends DuplexConnectionPool if (done.compareAndSet(false, true)) { boolean closed = isClosed(); - lock(); - try - { - if (LOG.isDebugEnabled()) - LOG.debug("Validated {}", connection); - quarantine.remove(connection); - if (!closed) - deactivate(connection); - } - finally - { - unlock(); - } - + if (LOG.isDebugEnabled()) + LOG.debug("Validated {}", connection); + quarantine.remove(connection); + if (!closed) + deactivate(connection); idle(connection, closed); proceed(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java index 871c6d7f382..505dc9ec3cf 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java @@ -30,13 +30,16 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HttpChannelOverHTTP extends HttpChannel { + private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHTTP.class); + private final HttpConnectionOverHTTP connection; private final HttpSenderOverHTTP sender; private final HttpReceiverOverHTTP receiver; - private final LongAdder inMessages = new LongAdder(); private final LongAdder outMessages = new LongAdder(); public HttpChannelOverHTTP(HttpConnectionOverHTTP connection) @@ -89,7 +92,6 @@ public class HttpChannelOverHTTP extends HttpChannel public void receive() { - inMessages.increment(); receiver.receive(); } @@ -145,7 +147,7 @@ public class HttpChannelOverHTTP extends HttpChannel protected long getMessagesIn() { - return inMessages.longValue(); + return receiver.getMessagesIn(); } protected long getMessagesOut() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java index 48f82012363..b2e0a38aab8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java @@ -44,12 +44,13 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.thread.Sweeper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HttpConnectionOverHTTP extends AbstractConnection implements IConnection, org.eclipse.jetty.io.Connection.UpgradeFrom, Sweeper.Sweepable +public class HttpConnectionOverHTTP extends AbstractConnection implements IConnection, org.eclipse.jetty.io.Connection.UpgradeFrom, Sweeper.Sweepable, Attachable { private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionOverHTTP.class); @@ -161,6 +162,18 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne return closed.get(); } + @Override + public void setAttachment(Object obj) + { + delegate.setAttachment(obj); + } + + @Override + public Object getAttachment() + { + return delegate.getAttachment(); + } + @Override public boolean onIdleExpired() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index efc29fe7789..d08d13b15ce 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.client.http; import java.io.EOFException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.LongAdder; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; @@ -45,6 +46,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res { private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP.class); + private final LongAdder inMessages = new LongAdder(); private final HttpParser parser; private RetainableByteBuffer networkBuffer; private boolean shutdown; @@ -343,9 +345,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res return false; int status = exchange.getResponse().getStatus(); - if (status != HttpStatus.CONTINUE_100) + { + inMessages.increment(); complete = true; + } return !responseSuccess(exchange); } @@ -386,6 +390,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res getHttpConnection().close(failure); } + long getMessagesIn() + { + return inMessages.longValue(); + } + @Override public String toString() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java index 2e084786d0c..e832bfe2d54 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java @@ -118,7 +118,7 @@ public class AsyncRequestContent implements Request.Content, Request.Content.Sub public void fail(Throwable failure) { List toFail = List.of(); - try (AutoLock ignored = lock.lock()) + try (AutoLock l = lock.lock()) { if (this.failure == null) { @@ -293,7 +293,7 @@ public class AsyncRequestContent implements Request.Content, Request.Content.Sub private void notifyFlush() { - try (AutoLock ignored = lock.lock()) + try (AutoLock l = lock.lock()) { flush.signal(); } @@ -301,7 +301,7 @@ public class AsyncRequestContent implements Request.Content, Request.Content.Sub public void flush() throws IOException { - try (AutoLock ignored = lock.lock()) + try (AutoLock l = lock.lock()) { try { @@ -327,7 +327,7 @@ public class AsyncRequestContent implements Request.Content, Request.Content.Sub public void close() { boolean produce = false; - try (AutoLock ignored = lock.lock()) + try (AutoLock l = lock.lock()) { if (closed) return; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java index dac81b63102..f943b1d2ed4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java @@ -22,9 +22,11 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -46,18 +48,33 @@ import org.eclipse.jetty.util.TypeUtil; */ public class DigestAuthentication extends AbstractAuthentication { + private final Random random; private final String user; private final String password; - /** + /** Construct a DigestAuthentication with a {@link SecureRandom} nonce. * @param uri the URI to match for the authentication * @param realm the realm to match for the authentication * @param user the user that wants to authenticate * @param password the password of the user */ public DigestAuthentication(URI uri, String realm, String user, String password) + { + this(uri, realm, user, password, new SecureRandom()); + } + + /** + * @param uri the URI to match for the authentication + * @param realm the realm to match for the authentication + * @param user the user that wants to authenticate + * @param password the password of the user + * @param random the Random generator to use for nonces. + */ + public DigestAuthentication(URI uri, String realm, String user, String password, Random random) { super(uri, realm); + Objects.requireNonNull(random); + this.random = random; this.user = user; this.password = password; } @@ -216,7 +233,6 @@ public class DigestAuthentication extends AbstractAuthentication private String newClientNonce() { - Random random = new Random(); byte[] bytes = new byte[8]; random.nextBytes(bytes); return toHexString(bytes); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java index 8f40c1217ad..3cddb02d3ba 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.client.AsyncContentProvider; @@ -102,15 +101,10 @@ public class MultiPartContentProvider extends AbstractTypedContentProvider imple private static String makeBoundary() { - Random random = new Random(); StringBuilder builder = new StringBuilder("JettyHttpClientBoundary"); - int length = builder.length(); - while (builder.length() < length + 16) - { - long rnd = random.nextLong(); - builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36)); - } - builder.setLength(length + 16); + builder.append(Long.toString(System.identityHashCode(builder), 36)); + builder.append(Long.toString(System.identityHashCode(Thread.currentThread()), 36)); + builder.append(Long.toString(System.nanoTime(), 36)); return builder.toString(); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolHelper.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolHelper.java new file mode 100644 index 00000000000..2c04dc5dcd2 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolHelper.java @@ -0,0 +1,34 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client; + +import org.eclipse.jetty.client.api.Connection; + +public class ConnectionPoolHelper +{ + public static Connection acquire(AbstractConnectionPool connectionPool, boolean create) + { + return connectionPool.acquire(create); + } + + public static void tryCreate(AbstractConnectionPool connectionPool, int pending) + { + connectionPool.tryCreate(pending); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java index b65202ed735..2b772ccd2fa 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java @@ -360,6 +360,74 @@ public class ConnectionPoolTest assertThat(connectionPool.getConnectionCount(), Matchers.lessThanOrEqualTo(count)); } + @ParameterizedTest + @MethodSource("pools") + public void testConnectionMaxUsage(ConnectionPoolFactory factory) throws Exception + { + startServer(new EmptyServerHandler()); + + int maxUsageCount = 2; + startClient(destination -> + { + AbstractConnectionPool connectionPool = (AbstractConnectionPool)factory.factory.newConnectionPool(destination); + connectionPool.setMaxUsageCount(maxUsageCount); + return connectionPool; + }); + client.setMaxConnectionsPerDestination(1); + + // Send first request, we are within the max usage count. + ContentResponse response1 = client.newRequest("localhost", connector.getLocalPort()).send(); + assertEquals(HttpStatus.OK_200, response1.getStatus()); + + HttpDestination destination = (HttpDestination)client.getDestinations().get(0); + AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool(); + + assertEquals(0, connectionPool.getActiveConnectionCount()); + assertEquals(1, connectionPool.getIdleConnectionCount()); + assertEquals(1, connectionPool.getConnectionCount()); + + // Send second request, max usage count will be reached, + // the only connection must be closed. + ContentResponse response2 = client.newRequest("localhost", connector.getLocalPort()).send(); + assertEquals(HttpStatus.OK_200, response2.getStatus()); + + assertEquals(0, connectionPool.getActiveConnectionCount()); + assertEquals(0, connectionPool.getIdleConnectionCount()); + assertEquals(0, connectionPool.getConnectionCount()); + } + + @ParameterizedTest + @MethodSource("pools") + public void testIdleTimeoutNoRequests(ConnectionPoolFactory factory) throws Exception + { + startServer(new EmptyServerHandler()); + startClient(destination -> + { + try + { + ConnectionPool connectionPool = factory.factory.newConnectionPool(destination); + connectionPool.preCreateConnections(1).get(); + return connectionPool; + } + catch (Exception x) + { + throw new RuntimeException(x); + } + }); + long idleTimeout = 1000; + client.setIdleTimeout(idleTimeout); + + // Trigger the creation of a destination, that will create the connection pool. + HttpDestination destination = client.resolveDestination(new Origin("http", "localhost", connector.getLocalPort())); + AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool(); + assertEquals(1, connectionPool.getConnectionCount()); + + // Wait for the pre-created connections to idle timeout. + Thread.sleep(idleTimeout + idleTimeout / 2); + + assertEquals(0, connectionPool.getConnectionCount()); + } + private static class ConnectionPoolFactory { private final String name; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java similarity index 77% rename from jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java rename to jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java index f6aadd4e7a1..8b0c3df6b24 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/DuplexHttpDestinationTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.client.http; +package org.eclipse.jetty.client; import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; @@ -24,21 +24,12 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import org.eclipse.jetty.client.AbstractHttpClientServerTest; -import org.eclipse.jetty.client.ConnectionPool; -import org.eclipse.jetty.client.DuplexConnectionPool; -import org.eclipse.jetty.client.DuplexHttpDestination; -import org.eclipse.jetty.client.EmptyServerHandler; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpDestination; -import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; -import org.eclipse.jetty.util.Callback; import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -52,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest +public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest { @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) @@ -67,7 +58,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest Connection connection = connectionPool.acquire(true); assertNull(connection); // There are no queued requests, so no connection should be created. - connection = pollIdleConnection(connectionPool, 1, TimeUnit.SECONDS); + connection = peekIdleConnection(connectionPool, 1, TimeUnit.SECONDS); assertNull(connection); } } @@ -78,19 +69,19 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest { start(scenario, new EmptyServerHandler()); - try (TestDestination destination = new TestDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) + try (HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); - TestDestination.TestConnectionPool connectionPool = (TestDestination.TestConnectionPool)destination.getConnectionPool(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger creation of one connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); - Connection connection = connectionPool.acquire(false); + Connection connection = ConnectionPoolHelper.acquire(connectionPool, false); if (connection == null) { // There are no queued requests, so the newly created connection will be idle - connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); + connection = peekIdleConnection(connectionPool, 5, TimeUnit.SECONDS); } assertNotNull(connection); } @@ -102,13 +93,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest { start(scenario, new EmptyServerHandler()); - try (TestDestination destination = new TestDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) + try (HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); - TestDestination.TestConnectionPool connectionPool = (TestDestination.TestConnectionPool)destination.getConnectionPool(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger creation of one connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); Connection connection1 = connectionPool.acquire(true); if (connection1 == null) @@ -131,12 +122,12 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest CountDownLatch idleLatch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1); - try (TestDestination destination = new TestDestination(client, new Origin("http", "localhost", connector.getLocalPort())) + try (HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort())) { @Override protected ConnectionPool newConnectionPool(HttpClient client) { - return new TestConnectionPool(this, client.getMaxConnectionsPerDestination(), this) + return new DuplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this) { @Override protected void onCreated(Connection connection) @@ -157,10 +148,10 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest }) { destination.start(); - TestDestination.TestConnectionPool connectionPool = (TestDestination.TestConnectionPool)destination.getConnectionPool(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger creation of one connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); // Make sure we entered idleCreated(). assertTrue(idleLatch.await(5, TimeUnit.SECONDS)); @@ -171,7 +162,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest assertNull(connection1); // Trigger creation of a second connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); // Second attempt also returns null because we delayed idleCreated() above. Connection connection2 = connectionPool.acquire(true); @@ -180,9 +171,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest latch.countDown(); // There must be 2 idle connections. - Connection connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); + Connection connection = peekIdleConnection(connectionPool, 5, TimeUnit.SECONDS); assertNotNull(connection); - connection = pollIdleConnection(connectionPool, 5, TimeUnit.SECONDS); + connection = peekIdleConnection(connectionPool, 5, TimeUnit.SECONDS); assertNotNull(connection); } } @@ -193,13 +184,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest { start(scenario, new EmptyServerHandler()); - try (TestDestination destination = new TestDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) + try (HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); - TestDestination.TestConnectionPool connectionPool = (TestDestination.TestConnectionPool)destination.getConnectionPool(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger creation of one connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); Connection connection1 = connectionPool.acquire(true); if (connection1 == null) @@ -230,13 +221,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest long idleTimeout = 1000; startClient(scenario, httpClient -> httpClient.setIdleTimeout(idleTimeout)); - try (TestDestination destination = new TestDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) + try (HttpDestination destination = new DuplexHttpDestination(client, new Origin("http", "localhost", connector.getLocalPort()))) { destination.start(); - TestDestination.TestConnectionPool connectionPool = (TestDestination.TestConnectionPool)destination.getConnectionPool(); + DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger creation of one connection. - connectionPool.tryCreate(1); + ConnectionPoolHelper.tryCreate(connectionPool, 1); Connection connection1 = connectionPool.acquire(true); if (connection1 == null) @@ -247,7 +238,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); - connection1 = connectionPool.getIdleConnections().poll(); + connection1 = connectionPool.getIdleConnections().peek(); assertNull(connection1); } } @@ -353,11 +344,6 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest assertTrue(client.getDestinations().isEmpty(), "Destination must be removed after connection error"); } - private Connection pollIdleConnection(DuplexConnectionPool connectionPool, long time, TimeUnit unit) throws InterruptedException - { - return await(() -> connectionPool.getIdleConnections().poll(), time, unit); - } - private Connection peekIdleConnection(DuplexConnectionPool connectionPool, long time, TimeUnit unit) throws InterruptedException { return await(() -> connectionPool.getIdleConnections().peek(), time, unit); @@ -375,38 +361,4 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest } return null; } - - private static class TestDestination extends DuplexHttpDestination - { - public TestDestination(HttpClient client, Origin origin) - { - super(client, origin); - } - - @Override - protected ConnectionPool newConnectionPool(HttpClient client) - { - return new TestConnectionPool(this, client.getMaxConnectionsPerDestination(), this); - } - - public static class TestConnectionPool extends DuplexConnectionPool - { - public TestConnectionPool(HttpDestination destination, int maxConnections, Callback requester) - { - super(destination, maxConnections, requester); - } - - @Override - public void tryCreate(int maxPending) - { - super.tryCreate(maxPending); - } - - @Override - public Connection acquire(boolean create) - { - return super.acquire(create); - } - } - } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index 491df8bdbea..1f066bb1bbe 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -661,7 +661,7 @@ public class HttpClientTLSTest HttpDestination destination = client.resolveDestination(origin); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger the creation of a new connection, but don't use it. - connectionPool.tryCreate(-1); + ConnectionPoolHelper.tryCreate(connectionPool, -1); // Verify that the connection has been created. while (true) { @@ -759,7 +759,7 @@ public class HttpClientTLSTest HttpDestination destination = client.resolveDestination(origin); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); // Trigger the creation of a new connection, but don't use it. - connectionPool.tryCreate(-1); + ConnectionPoolHelper.tryCreate(connectionPool, -1); // Verify that the connection has been created. while (true) { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 6cdd89ccbac..850eb5152f2 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -77,7 +77,6 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.logging.StacklessLogging; @@ -1603,29 +1602,32 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - public void testIPv6Host(Scenario scenario) throws Exception + public void testIPv6HostWithHTTP10(Scenario scenario) throws Exception { Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - baseRequest.setHandled(true); response.setContentType("text/plain"); - response.getOutputStream().print(request.getHeader("Host")); + response.getOutputStream().print(request.getServerName()); } }); URI uri = URI.create(scenario.getScheme() + "://[::1]:" + connector.getLocalPort() + "/path"); ContentResponse response = client.newRequest(uri) .method(HttpMethod.PUT) + .version(HttpVersion.HTTP_1_0) + .onRequestBegin(r -> r.headers(headers -> headers.remove(HttpHeader.HOST))) .timeout(5, TimeUnit.SECONDS) .send(); assertNotNull(response); assertEquals(200, response.getStatus()); - assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); + String content = response.getContentAsString(); + assertThat(content, Matchers.startsWith("[")); + assertThat(content, Matchers.endsWith(":1]")); } @ParameterizedTest diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java index 6b753388337..9d356843fec 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java @@ -67,8 +67,10 @@ public class HttpClientURITest extends AbstractHttpClientServerTest Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); start(scenario, new EmptyServerHandler()); - String host = "::1"; - Request request = client.newRequest(host, connector.getLocalPort()) + String hostAddress = "::1"; + String host = "[" + hostAddress + "]"; + // Explicitly use a non-bracketed IPv6 host. + Request request = client.newRequest(hostAddress, connector.getLocalPort()) .scheme(scenario.getScheme()) .timeout(5, TimeUnit.SECONDS); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index a06fd83957d..f430b9db61f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -74,17 +74,14 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest HttpDestination destination = (HttpDestination)client.resolveDestination(request); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); - Collection idleConnections = connectionPool.getIdleConnections(); - assertEquals(0, idleConnections.size()); - - Collection activeConnections = connectionPool.getActiveConnections(); - assertEquals(0, activeConnections.size()); + assertEquals(0, connectionPool.getIdleConnections().size()); + assertEquals(0, connectionPool.getActiveConnections().size()); request.onRequestSuccess(r -> successLatch.countDown()) .onResponseHeaders(response -> { - assertEquals(0, idleConnections.size()); - assertEquals(1, activeConnections.size()); + assertEquals(0, ((DuplexConnectionPool)destination.getConnectionPool()).getIdleConnections().size()); + assertEquals(1, ((DuplexConnectionPool)destination.getConnectionPool()).getActiveConnections().size()); headersLatch.countDown(); }) .send(new Response.Listener.Adapter() @@ -106,8 +103,8 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest assertTrue(headersLatch.await(30, TimeUnit.SECONDS)); assertTrue(successLatch.await(30, TimeUnit.SECONDS)); - assertEquals(1, idleConnections.size()); - assertEquals(0, activeConnections.size()); + assertEquals(1, ((DuplexConnectionPool)destination.getConnectionPool()).getIdleConnections().size()); + assertEquals(0, ((DuplexConnectionPool)destination.getConnectionPool()).getActiveConnections().size()); } @ParameterizedTest @@ -124,18 +121,15 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest HttpDestination destination = (HttpDestination)client.resolveDestination(request); DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool(); - Collection idleConnections = connectionPool.getIdleConnections(); - assertEquals(0, idleConnections.size()); - - Collection activeConnections = connectionPool.getActiveConnections(); - assertEquals(0, activeConnections.size()); + assertEquals(0, connectionPool.getIdleConnections().size()); + assertEquals(0, connectionPool.getActiveConnections().size()); request.listener(new Request.Listener.Adapter() { @Override public void onBegin(Request request) { - activeConnections.iterator().next().close(); + connectionPool.getActiveConnections().iterator().next().close(); beginLatch.countDown(); } @@ -144,24 +138,23 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest { failureLatch.countDown(); } - }) - .send(new Response.Listener.Adapter() + }).send(new Response.Listener.Adapter() + { + @Override + public void onComplete(Result result) { - @Override - public void onComplete(Result result) - { - assertTrue(result.isFailed()); - assertEquals(0, idleConnections.size()); - assertEquals(0, activeConnections.size()); - failureLatch.countDown(); - } - }); + assertTrue(result.isFailed()); + assertEquals(0, connectionPool.getIdleConnections().size()); + assertEquals(0, connectionPool.getActiveConnections().size()); + failureLatch.countDown(); + } + }); assertTrue(beginLatch.await(30, TimeUnit.SECONDS)); assertTrue(failureLatch.await(30, TimeUnit.SECONDS)); - assertEquals(0, idleConnections.size()); - assertEquals(0, activeConnections.size()); + assertEquals(0, connectionPool.getIdleConnections().size()); + assertEquals(0, connectionPool.getActiveConnections().size()); } @ParameterizedTest diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java index fa70c43229d..036aaacbd79 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java @@ -89,7 +89,10 @@ public class InputStreamContentTest server.stop(); } - private static List> content() + /** + * need public access to avoid jpms issue + */ + public static List> content() { return List.of( (request, stream) -> request.body(new InputStreamRequestContent(stream)), 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 3793d99f7b6..04c4bd90143 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 @@ -47,6 +47,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.xml.XmlConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,6 +126,7 @@ public class DeploymentManager extends ContainerLifeCycle } } + private final AutoLock _lock = new AutoLock(); private final List _providers = new ArrayList(); private final AppLifeCycle _lifecycle = new AppLifeCycle(); private final Queue _apps = new ConcurrentLinkedQueue(); @@ -562,13 +564,14 @@ public class DeploymentManager extends ContainerLifeCycle requestAppGoal(appentry, nodeName); } - private synchronized void addOnStartupError(Throwable cause) + private void addOnStartupError(Throwable cause) { - if (onStartupErrors == null) + try (AutoLock l = _lock.lock()) { - onStartupErrors = new MultiException(); + if (onStartupErrors == null) + onStartupErrors = new MultiException(); + onStartupErrors.add(cause); } - onStartupErrors.add(cause); } /** diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/deploying/overlay-deployer.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/deploying/overlay-deployer.adoc deleted file mode 100644 index 560e3ea2c80..00000000000 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/deploying/overlay-deployer.adoc +++ /dev/null @@ -1,292 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[overlay-deployer]] -=== Overlay WebApp Deployer - -____ -[NOTE] -This feature was reintroduced in Jetty 9.0.4 -____ - -The Jetty Overlay Deployer allows multiple WAR files to be overlaid so that a web application can be customized, configured, and deployed without unpacking, modifying and repacking the WAR file. -This has the following benefits: - -* WAR files can be kept immutable, even signed, so that it is clear which version is deployed. -* All modifications made to customize/configure the web application are separate WARs, and thus are easily identifiable for review and migration to new versions. -* A parameterized template overlay can be created that contains common customizations and configuration that apply to many instances of the web application (for example, for multi-tenant deployment). -* Because the layered deployment clearly identifies the common and instance specific components, Jetty is able to share classloaders and static resource caches for the template, greatly reducing the memory footprint of multiple instances. - -This tutorial describes how to configure Jetty to use the Overlay Deployer, and how to deploy multiple instances of a web application using the JTrac application in the example. - -[[overlay-overview]] -==== Overview - -Customizing, configuring and deploying a web application bundled as a WAR file frequently includes some or all of these steps: - -* Editing the `WEB-INF/web.xml` file to set init parameters, add filters/servlets or to configure JNDI resources. -* Editing other application specific configuration files under `WEB-INF/`. -* Editing container specific configuration files under `WEB-INF/` (for example, `jetty-web.xml` or `jboss-web.xml`). -* Adding/modifying static content such as images and CSS to create a style or themes for the web application. -* Adding Jars to the container classpath for Datasource and other resources. -* Modifying the container configuration to provide JNDI resources. - -The result is that the customizations and configurations blend into both the container and the WAR file. -If either the container or the base WAR file is upgraded to a new version, it can be a very difficult and error prone task to identify all the changes that have been made and to reapply them to a new version. - -[[overlay-overlays]] -==== Overlays - -To solve the problems highlighted above, Jetty introduced WAR overlays (a concept borrowed from the Maven WAR plugin). -An overlay is basically just another WAR file, whose contents merge on top of the original WAR so that filed can be added or replaced. -Jetty overlays also allow fragments of `web.xml` to be mixed in, which means the configuration can be modified without replacing it. - -[[overlay-jtrac]] -==== JTrac Overlay Example - -The JTrac issue tracking web application is a good example of a typical web application, as it uses the usual suspects of libs: spring, hibernate, dom4j, commons-*, wicket, etc. -The files for this demonstration are available in overlays-demo.tar.gz. -The demonstration can be expanded on top of the Jetty distribution; this tutorial expands it to /tmp and installs the components step-by-step: - -[source, screen, subs="{sub-order}"] ----- -$ cd /tmp -$ wget http://webtide.com/wp-content/uploads/2011/05/overlays-demo.tar.gz -$ tar xfvz overlays-demo.tar.gz -$ export OVERLAYS=/tmp/overlays ----- - -[[overlay-configure]] -==== Configuring Jetty for Overlays - -Overlays support is included in jetty distributions from 7.4.1-SNAPSHOT onwards, which can be downloaded from oss.sonatype.org or Maven Central and unpack into a directory. -The `start.ini` file needs edited so that it includes the overlay option and configuration file. -The resulting file should look like: - -[source, plain, subs="{sub-order}"] ----- -OPTIONS=Server,jsp,jmx,resources,websocket,ext,overlay -etc/jetty.xml -etc/jetty-deploy.xml -etc/jetty-overlay.xml ----- - -The mechanics of this are in etc/jetty-deploy.xml, which installs the `OverlayedAppProvider` into the `DeploymentManager`. -Jetty can then be started normally: - -[source, screen, subs="{sub-order}"] ----- -$ java -jar start.jar ----- - -Jetty is now listening on port 8080, but with no webapp deployed. - -____ -[IMPORTANT] -You should conduct the rest of the tutorial in another window with the JETTY_HOME environmental variable set to the Jetty distribution directory. -____ - -[[overlay-install]] -==== Installing the WebApp - -The WAR file for this demo can be downloaded and deployed the using the following commands, which downloads and extracts the WAR file to the $JETTY_HOME/overlays/webapps directory. - -[source, screen, subs="{sub-order}"] ----- -$ cd /tmp -$ wget -O jtrac.zip http://sourceforge.net/projects/j-trac/files/jtrac/2.1.0/jtrac-2.1.0.zip/download -$ jar xfv jtrac.zip jtrac/jtrac.war -$ mv jtrac/jtrac.war $JETTY_HOME/overlays/webapps ----- - -When these commands (or equivalent) have been executed, a message that the `OverlayedAppProvider` has extracted and loaded the WAR file will be displayed in the Jetty server window: - -[source, plain, subs="{sub-order}"] ----- -2011-05-06 10:31:54.678:INFO:OverlayedAppProvider:Extract jar:file:/tmp/jetty-distribution-7.4.1-SNAPSHOT/overlays/webapps/jtrac-2.1.0.war!/ to /tmp/jtrac-2.1.0_236811420856825222.extract -2011-05-06 10:31:55.235:INFO:OverlayedAppProvider:loaded jtrac-2.1.0@1304641914666 ----- - -Unlike the normal webapps dir, loading a WAR file from the overlays/webapp dir does not deploy the web application, it simply makes it available to use as the basis for templates and overlays. - -==== Installing a Template Overlay - -A template overlay is a WAR structured directory/archive that contains the files that have been added or modified to customize/configure the web application for all instances planned for deployment. - -The demo template can be installed from the downloaded files with the command: - -[source, screen, subs="{sub-order}"] ----- -$ mv $OVERLAYS/jtracTemplate\=jtrac-2.1.0 $JETTY_HOME/overlays/templates/ ----- - -In the Jetty server window, a message similar to this will be displayed confirmed that the template is loaded: - -[source, plain, subs="{sub-order}"] ----- -2011-05-06 11:00:08.716:INFO:OverlayedAppProvider:loaded jtracTemplate=jtrac-2.1.0@1304643608715 ----- - -The contents of the loaded template are as follows: - -[source, plain, subs="{sub-order}"] ----- -templates/jtracTemplate=jtrac-2.1.0 -|__ WEB-INF - |__ classes - | |__ jtrac-init.properties - |__ log4j.properties - |__ overlay.xml - |__ template.xml - |__ web-overlay.xml ----- - -name of the template directory (or WAR):: - Uses the ‘=’ character in jtracTemplate=jtrac-2.1.0 to separate the name of the template from the name of the WAR file in webapps that it applies to. - If = is a problem, use -- instead. -WEB-INF/classes/jtrac-init.properties:: - Replaces the JTrac properties file with an empty file, as the properties it contains are configured elsewhere. -WEB-INF/log4j.properties:: - Configures the logging for all instances of the template. -WEB-INF/overlay.xml:: - A Jetty XML formatted IoC file that injects/configures the `ContextHandler` for each instance. \ - In this case it sets up the context path: - -[source, xml, subs="{sub-order}"] ----- - - - - / - ----- - -WEB-INF/template.xml:: - A Jetty XML formatted IoC file that injects/configures the resource cache and classloader that all instances of the template share. - It runs only once per load of the template: - -[source, xml, subs="{sub-order}"] ----- - - - - - true - 10000000 - 1000 - 64000000 - - ----- - -WEB-INF/web-overlay.xml:: - A `web.xml` fragment that Jetty overlays on top of the `web.xml` from the base WAR file; it can set init parameters and add/modify filters and - servlets. - In this example it sets the application home and springs `webAppRootKey`: - -[source, xml, subs="{sub-order}"] ----- - - - - jtrac.home - /tmp/jtrac-${overlay.instance.classifier} - - - webAppRootKey - jtrac-${overlay.instance.classifier} - - - ----- - -Notice the parameterization of values such as `${overlays.instance.classifier}`, as this allows the configuration to be in the template, and not customized for each instance. - -Without the Overlay Deployer, all of the above would still need to have configure , but rather than being in a single clear structure the configuration elements would have been either in the server's common directory, the server's `webdefaults.xml` (aka `server.xml`), or baked into the WAR file of each application instance using copied/modified files from the original. -The Overlay Deployer allows all these changes to be made in one structure; moreover it allows for the parameterization of some of the configuration, which facilitates easy multi-tenant deployment. - -==== Installing an Instance Overlay - -Now that the template is installed, one or more instance overlays can be implemented to deploy the actual web applications: - -[source, screen, subs="{sub-order}"] ----- -$ mv /tmp/overlays/instances/jtracTemplate\=blue $JETTY_HOME/overlays/instances/ -$ mv /tmp/overlays/instances/jtracTemplate\=red $JETTY_HOME/overlays/instances/ -$ mv /tmp/overlays/instances/jtracTemplate\=blue $JETTY_HOME/overlays/instances/ ----- - -As each instance moves into place, the Jetty server window reacts and deploys the instance. -Within each instance, there is the structure: - -[source, plain, subs="{sub-order}"] ----- -instances/jtracTemplate=red/ -|__ WEB-INF -| |__ overlay.xml -|__ favicon.ico -|__ resources - |__ jtrac.css ----- - -WEB-INF/overlay.xml:: - A Jetty XML format IoC file that injects/configures the context for the instance. - In this case it sets up a virtual host for the instance: - -[source, xml, subs="{sub-order}"] ----- - - - - - - 127.0.0.2 - red.myVirtualDomain.com - - - ----- - -favicon.ico:: - Replaces the icon in the base WAR with one that has a theme for the instance; in this case red, blue, or green. -resources/jtrac.css:: - Replaces the style sheet from the base WAR with one that has a theme for the instance. - -Deployed instances can be vied by pointing a browser at http://127.0.0.1:8080, http://127.0.0.2:8080 and http://127.0.0.3:8080. -The default username/password for JTrac is admin/admin. - -[[overlay-tips]] -==== Things to Know and Notice - -* Each instance has themes with images and style sheets from the instance overlay. -* Each instance is running with its own application directory (that is, /tmp/jtrac-red), set in templates web-overlay.xml. -* A virtual host set in the instance overlay.xml distinguishes the instances. -* All instances share static content from the base WAR and template. -Specifically there is a shared `ResourceCache` so only a single instance of each static content is loaded into memory. -* All instances share the classloader at the base WAR and template level, so that only a single instance of common classes is loaded into memory. -Classes with non shared statics can be configured to load in the instances classloader. -* Jetty hot deploys all overlays and tracks dependencies. -** If an XML changes in an instance, Jetty redeploys it. -** If an XML changes in a template, then Jetty redeploys all instances using it. -** If a WAR file changes, then Jetty redeploys all templates and all instances dependent on it. -* New versions can be easily deployed. -For example, when JTrac-2.2.0.war becomes available, it can be placed into `overlays/webapps` and then rename `jtracTemplate\=jtrac-2.1.0` to `jtracTemplate\=jtrac-2.2.0`. -* There is a fuller version of this demo in overlays-demo-jndi.tar.gz, that uses JNDI (needs `options=jndi`, annotations and `jetty-plus.xml` in `start.ini`) and shows how additional JARs can be added in the overlays. diff --git a/jetty-documentation/src/main/asciidoc/distribution-guide/security/setting-port80-access-for-non-root-user.adoc b/jetty-documentation/src/main/asciidoc/distribution-guide/security/setting-port80-access-for-non-root-user.adoc index 20c761836eb..8615ec28ed9 100644 --- a/jetty-documentation/src/main/asciidoc/distribution-guide/security/setting-port80-access-for-non-root-user.adoc +++ b/jetty-documentation/src/main/asciidoc/distribution-guide/security/setting-port80-access-for-non-root-user.adoc @@ -102,8 +102,8 @@ As well as opening the connectors as `root`, you can also have Jetty start the S ____ . A native code library is required to perform user switching. -This code is hosted as part of the Jetty ToolChain project and is released independently from Jetty itself. -You can find the source code https://github.com/eclipsejetty.toolchain[here] in the https://github.com/eclipse/jetty.toolchain/jetty-setuid[jetty-setuid] project. +This code is hosted as part of the https://github.com/eclipse/jetty.toolchain[Jetty ToolChain] project and is released independently from Jetty itself. +You can find the source code in the https://github.com/eclipse/jetty.toolchain/tree/master/jetty-setuid[eclipse/jetty.toolchain/jetty-setuid] project. Build it locally, which will produce a native library appropriate for the operating system: + [source, screen, subs="{sub-order}"] diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 61c8cd25033..6a8dc16fb56 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -33,9 +33,13 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.IdleTimeout; import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HttpChannelOverFCGI extends HttpChannel { + private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class); + private final HttpConnectionOverFCGI connection; private final Flusher flusher; private final HttpSenderOverFCGI sender; diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 16badda409a..6dfbe2728ea 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -51,16 +51,19 @@ import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.RetainableByteBuffer; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HttpConnectionOverFCGI extends AbstractConnection implements IConnection +public class HttpConnectionOverFCGI extends AbstractConnection implements IConnection, Attachable { private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionOverFCGI.class); + private final AutoLock lock = new AutoLock(); private final LinkedList requests = new LinkedList<>(); private final Map activeChannels = new ConcurrentHashMap<>(); private final Queue idleChannels = new ConcurrentLinkedQueue<>(); @@ -71,6 +74,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne private final Delegate delegate; private final ClientParser parser; private RetainableByteBuffer networkBuffer; + private Object attachment; public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, Promise promise) { @@ -265,6 +269,18 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne return closed.get(); } + @Override + public void setAttachment(Object obj) + { + this.attachment = obj; + } + + @Override + public Object getAttachment() + { + return attachment; + } + protected boolean closeByHTTP(HttpFields fields) { if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) @@ -307,7 +323,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne private int acquireRequest() { - synchronized (requests) + try (AutoLock l = lock.lock()) { int last = requests.getLast(); int request = last + 1; @@ -318,7 +334,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne private void releaseRequest(int request) { - synchronized (requests) + try (AutoLock l = lock.lock()) { requests.removeFirstOccurrence(request); } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index 0b31c501a4a..9263cbb4450 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -24,6 +24,7 @@ import java.util.Queue; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +32,7 @@ public class Flusher { private static final Logger LOG = LoggerFactory.getLogger(Flusher.class); + private final AutoLock lock = new AutoLock(); private final Queue queue = new ArrayDeque<>(); private final IteratingCallback flushCallback = new FlushCallback(); private final EndPoint endPoint; @@ -51,7 +53,7 @@ public class Flusher private void offer(Generator.Result result) { - synchronized (this) + try (AutoLock l = lock.lock()) { queue.offer(result); } @@ -59,7 +61,7 @@ public class Flusher private Generator.Result poll() { - synchronized (this) + try (AutoLock l = lock.lock()) { return queue.poll(); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index dbcecee56bf..d042c1e2c4a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -384,11 +384,9 @@ public class HttpField return _name.equalsIgnoreCase(field.getName()); } - @Override - public String toString() + public boolean is(String name) { - String v = getValue(); - return getName() + ": " + (v == null ? "" : v); + return _name.equalsIgnoreCase(name); } private int nameHashCode() @@ -411,6 +409,13 @@ public class HttpField return h; } + @Override + public String toString() + { + String v = getValue(); + return getName() + ": " + (v == null ? "" : v); + } + public static class IntValueHttpField extends HttpField { private final int _int; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 4924bb78b17..673c8d75020 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -26,9 +26,12 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -119,7 +122,7 @@ public interface HttpFields extends Iterable { for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name) && f.contains(value)) + if (f.is(name) && f.contains(value)) return true; } return false; @@ -149,7 +152,7 @@ public interface HttpFields extends Iterable { for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) return true; } return false; @@ -169,7 +172,7 @@ public interface HttpFields extends Iterable { for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(header)) + if (f.is(header)) return f.getValue(); } return null; @@ -211,7 +214,7 @@ public interface HttpFields extends Iterable QuotedCSV values = null; for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (values == null) values = new QuotedCSV(keepQuotes); @@ -266,7 +269,7 @@ public interface HttpFields extends Iterable { for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) return f; } return null; @@ -301,7 +304,19 @@ public interface HttpFields extends Iterable */ default List getFields(HttpHeader header) { - return stream().filter(f -> f.getHeader().equals(header)).collect(Collectors.toList()); + return getFields(header, (f, h) -> f.getHeader() == h); + } + + default List getFields(String name) + { + return getFields(name, (f, n) -> f.is(name)); + } + + private List getFields(T header, BiPredicate predicate) + { + return stream() + .filter(f -> predicate.test(f, header)) + .collect(Collectors.toList()); } /** @@ -380,7 +395,7 @@ public interface HttpFields extends Iterable QuotedQualityCSV values = null; for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (values == null) values = new QuotedQualityCSV(); @@ -411,7 +426,7 @@ public interface HttpFields extends Iterable while (i.hasNext()) { HttpField f = i.next(); - if (f.getName().equalsIgnoreCase(name) && f.getValue() != null) + if (f.is(name) && f.getValue() != null) { _field = f; return true; @@ -462,7 +477,7 @@ public interface HttpFields extends Iterable final List list = new ArrayList<>(); for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) list.add(f.getValue()); } return list; @@ -685,7 +700,7 @@ public interface HttpFields extends Iterable QuotedCSV existing = null; for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (existing == null) existing = new QuotedCSV(false); @@ -781,7 +796,7 @@ public interface HttpFields extends Iterable { if (_size == 0) throw new IllegalStateException(); - System.arraycopy(_fields, _index, _fields, _index - 1, _size-- - _index--); + Mutable.this.remove(--_index); } }; } @@ -915,6 +930,152 @@ public interface HttpFields extends Iterable return put(name, Long.toString(value)); } + /** + *

Computes a single field for the given HttpHeader and for existing fields with the same header.

+ * + *

The compute function receives the field name and a list of fields with the same name + * so that their values can be used to compute the value of the field that is returned + * by the compute function. + * If the compute function returns {@code null}, the fields with the given name are removed.

+ *

This method comes handy when you want to add an HTTP header if it does not exist, + * or add a value if the HTTP header already exists, similarly to + * {@link Map#compute(Object, BiFunction)}.

+ * + *

This method can be used to {@link #put(HttpField) put} a new field (or blindly replace its value):

+ *
+         * httpFields.computeField("X-New-Header",
+         *     (name, fields) -> new HttpField(name, "NewValue"));
+         * 
+ * + *

This method can be used to coalesce many fields into one:

+ *
+         * // Input:
+         * GET / HTTP/1.1
+         * Host: localhost
+         * Cookie: foo=1
+         * Cookie: bar=2,baz=3
+         * User-Agent: Jetty
+         *
+         * // Computation:
+         * httpFields.computeField("Cookie", (name, fields) ->
+         * {
+         *     // No cookies, nothing to do.
+         *     if (fields == null)
+         *         return null;
+         *
+         *     // Coalesces all cookies.
+         *     String coalesced = fields.stream()
+         *         .flatMap(field -> Stream.of(field.getValues()))
+         *         .collect(Collectors.joining(", "));
+         *
+         *     // Returns a single Cookie header with all cookies.
+         *     return new HttpField(name, coalesced);
+         * }
+         *
+         * // Output:
+         * GET / HTTP/1.1
+         * Host: localhost
+         * Cookie: foo=1, bar=2, baz=3
+         * User-Agent: Jetty
+         * 
+ * + *

This method can be used to replace a field:

+ *
+         * httpFields.computeField("X-Length", (name, fields) ->
+         * {
+         *     if (fields == null)
+         *         return null;
+         *
+         *     // Get any value among the X-Length headers.
+         *     String length = fields.stream()
+         *         .map(HttpField::getValue)
+         *         .findAny()
+         *         .orElse("0");
+         *
+         *     // Replace X-Length headers with X-Capacity header.
+         *     return new HttpField("X-Capacity", length);
+         * });
+         * 
+ * + *

This method can be used to remove a field:

+ *
+         * httpFields.computeField("Connection", (name, fields) -> null);
+         * 
+ * + * @param header the HTTP header + * @param computeFn the compute function + */ + public void computeField(HttpHeader header, BiFunction, HttpField> computeFn) + { + computeField(header, computeFn, (f, h) -> f.getHeader() == h); + } + + /** + *

Computes a single field for the given HTTP header name and for existing fields with the same name.

+ * + * @param name the HTTP header name + * @param computeFn the compute function + * @see #computeField(HttpHeader, BiFunction) + */ + public void computeField(String name, BiFunction, HttpField> computeFn) + { + computeField(name, computeFn, HttpField::is); + } + + private void computeField(T header, BiFunction, HttpField> computeFn, BiPredicate matcher) + { + // Look for first occurrence + int first = -1; + for (int i = 0; i < _size; i++) + { + HttpField f = _fields[i]; + if (matcher.test(f, header)) + { + first = i; + break; + } + } + + // If the header is not found, add a new one; + if (first < 0) + { + HttpField newField = computeFn.apply(header, null); + if (newField != null) + add(newField); + return; + } + + // Are there any more occurrences? + List found = null; + for (int i = first + 1; i < _size; i++) + { + HttpField f = _fields[i]; + if (matcher.test(f, header)) + { + if (found == null) + { + found = new ArrayList<>(); + found.add(_fields[first]); + } + // Remember and remove additional fields + found.add(f); + remove(i--); + } + } + + // If no additional fields were found, handle singleton case + if (found == null) + found = Collections.singletonList(_fields[first]); + else + found = Collections.unmodifiableList(found); + + HttpField newField = computeFn.apply(header, found); + if (newField == null) + remove(first); + else + _fields[first] = newField; + } + /** * Remove a field. * @@ -927,7 +1088,7 @@ public interface HttpFields extends Iterable { HttpField f = _fields[i]; if (f.getHeader() == name) - System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1); + remove(i--); } return this; } @@ -938,7 +1099,7 @@ public interface HttpFields extends Iterable { HttpField f = _fields[i]; if (fields.contains(f.getHeader())) - System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1); + remove(i--); } return this; } @@ -954,12 +1115,19 @@ public interface HttpFields extends Iterable for (int i = 0; i < _size; i++) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) - System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1); + if (f.is(name)) + remove(i--); } return this; } + private void remove(int i) + { + _size--; + System.arraycopy(_fields, i + 1, _fields, i, _size - i); + _fields[_size] = null; + } + public int size() { return _size; @@ -1077,9 +1245,7 @@ public interface HttpFields extends Iterable { if (_current < 0) throw new IllegalStateException(); - _size--; - System.arraycopy(_fields, _current + 1, _fields, _current, _size - _current); - _fields[_size] = null; + Mutable.this.remove(_current); _cursor = _current; _current = -1; } @@ -1141,7 +1307,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.getName().equalsIgnoreCase(header)) + if (f.is(header)) return f.getValue(); return null; } @@ -1171,7 +1337,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) return f; return null; } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index f377b566cfb..2eb61902cd1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -291,7 +291,7 @@ public class MimeTypes { } - public synchronized Map getMimeMap() + public Map getMimeMap() { return _mimeMap; } 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 ca8cc8dca15..e70a9322a7b 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 @@ -30,6 +30,7 @@ import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.stream.Collectors; import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.Matchers; @@ -38,6 +39,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -879,4 +881,45 @@ public class HttpFieldsTest assertThat(i.next().getName(), is("name4")); assertThat(i.hasNext(), is(false)); } + + @Test + public void testStream() + { + HttpFields.Mutable fields = HttpFields.build(); + assertThat(fields.stream().count(), is(0L)); + fields.put("name1", "valueA"); + fields.put("name2", "valueB"); + fields.add("name3", "valueC"); + assertThat(fields.stream().count(), is(3L)); + assertThat(fields.stream().map(HttpField::getName).filter("name2"::equalsIgnoreCase).count(), is(1L)); + } + + @Test + public void testComputeField() + { + HttpFields.Mutable fields = HttpFields.build(); + assertThat(fields.size(), is(0)); + + fields.computeField("Test", (n, f) -> null); + assertThat(fields.size(), is(0)); + + fields.add(new HttpField("Before", "value")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value")); + + fields.computeField("Test", (n, f) -> new HttpField(n, "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one")); + + fields.add(new HttpField("After", "value")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value")); + + fields.add(new HttpField("Test", "two")); + fields.add(new HttpField("Test", "three")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value", "Test: two", "Test: three")); + + fields.computeField("Test", (n, f) -> new HttpField("TEST", "count=" + f.size())); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "TEST: count=3", "After: value")); + + fields.computeField("TEST", (n, f) -> null); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "After: value")); + } } diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/RawHTTP2ProxyTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/RawHTTP2ProxyTest.java index 3ebfc00e557..49bc7131ebd 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/RawHTTP2ProxyTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/RawHTTP2ProxyTest.java @@ -53,6 +53,7 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -316,7 +317,7 @@ public class RawHTTP2ProxyTest private static class ClientToProxyToServer extends IteratingCallback implements Stream.Listener { - private final Object lock = this; + private final AutoLock lock = new AutoLock(); private final Map> frames = new HashMap<>(); private final Map streams = new HashMap<>(); private final ServerToProxyToClient serverToProxyToClient = new ServerToProxyToClient(); @@ -339,7 +340,7 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS queueing {} for {} on {}", frame, stream, stream.getSession()); boolean connected; - synchronized (lock) + try (AutoLock l = lock.lock()) { Deque deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>()); deque.offer(new FrameInfo(frame, callback)); @@ -363,7 +364,7 @@ public class RawHTTP2ProxyTest { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS connected to {} with {}", address, result); - synchronized (lock) + try (AutoLock l = lock.lock()) { proxyToServerSession = result; } @@ -385,7 +386,7 @@ public class RawHTTP2ProxyTest { Stream proxyToServerStream = null; Session proxyToServerSession = null; - synchronized (lock) + try (AutoLock l = lock.lock()) { for (Map.Entry> entry : frames.entrySet()) { @@ -415,7 +416,7 @@ public class RawHTTP2ProxyTest @Override public void succeeded(Stream result) { - synchronized (lock) + try (AutoLock l = lock.lock()) { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS created {}", result); @@ -549,7 +550,7 @@ public class RawHTTP2ProxyTest private static class ServerToProxyToClient extends IteratingCallback implements Stream.Listener { - private final Object lock = this; + private final AutoLock lock = new AutoLock(); private final Map> frames = new HashMap<>(); private final Map streams = new HashMap<>(); private FrameInfo frameInfo; @@ -559,7 +560,7 @@ public class RawHTTP2ProxyTest protected Action process() throws Throwable { Stream proxyToClientStream = null; - synchronized (lock) + try (AutoLock l = lock.lock()) { for (Map.Entry> entry : frames.entrySet()) { @@ -630,7 +631,7 @@ public class RawHTTP2ProxyTest { if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC queueing {} for {} on {}", frame, stream, stream.getSession()); - synchronized (lock) + try (AutoLock l = lock.lock()) { Deque deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>()); deque.offer(new FrameInfo(frame, callback)); @@ -682,7 +683,7 @@ public class RawHTTP2ProxyTest private void link(Stream proxyToServerStream, Stream clientToProxyStream) { - synchronized (lock) + try (AutoLock l = lock.lock()) { streams.put(proxyToServerStream, clientToProxyStream); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java index 206b869e58e..f06f4d936ff 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.TryExecutor; import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill; @@ -49,6 +50,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. // TODO remove this once we are sure EWYK is OK for http2 private static final boolean PEC_MODE = Boolean.getBoolean("org.eclipse.jetty.http2.PEC_MODE"); + private final AutoLock lock = new AutoLock(); private final Queue tasks = new ArrayDeque<>(); private final HTTP2Producer producer = new HTTP2Producer(); private final AtomicLong bytesIn = new AtomicLong(); @@ -190,7 +192,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private void offerTask(Runnable task) { - synchronized (this) + try (AutoLock l = lock.lock()) { tasks.offer(task); } @@ -220,7 +222,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher. private Runnable pollTask() { - synchronized (this) + try (AutoLock l = lock.lock()) { return tasks.poll(); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java index 93ffc90c3ac..8d2044f92b7 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Flusher.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +47,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable private static final Logger LOG = LoggerFactory.getLogger(HTTP2Flusher.class); private static final ByteBuffer[] EMPTY_BYTE_BUFFERS = new ByteBuffer[0]; + private final AutoLock lock = new AutoLock(); private final Queue windows = new ArrayDeque<>(); private final Deque entries = new ArrayDeque<>(); private final Queue pendingEntries = new ArrayDeque<>(); @@ -64,7 +66,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public void window(IStream stream, WindowUpdateFrame frame) { Throwable closed; - synchronized (this) + try (AutoLock l = lock.lock()) { closed = terminated; if (closed == null) @@ -78,7 +80,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public boolean prepend(Entry entry) { Throwable closed; - synchronized (this) + try (AutoLock l = lock.lock()) { closed = terminated; if (closed == null) @@ -97,7 +99,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public boolean append(Entry entry) { Throwable closed; - synchronized (this) + try (AutoLock l = lock.lock()) { closed = terminated; if (closed == null) @@ -115,7 +117,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable private int getWindowQueueSize() { - synchronized (this) + try (AutoLock l = lock.lock()) { return windows.size(); } @@ -123,7 +125,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable public int getFrameQueueSize() { - synchronized (this) + try (AutoLock l = lock.lock()) { return entries.size(); } @@ -135,7 +137,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable if (LOG.isDebugEnabled()) LOG.debug("Flushing {}", session); - synchronized (this) + try (AutoLock l = lock.lock()) { if (terminated != null) throw terminated; @@ -323,7 +325,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable Throwable closed; Set allEntries; - synchronized (this) + try (AutoLock l = lock.lock()) { closed = terminated; terminated = x; @@ -352,7 +354,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable void terminate(Throwable cause) { Throwable closed; - synchronized (this) + try (AutoLock l = lock.lock()) { closed = terminated; terminated = cause; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 107cb15be93..9169fd2cb0b 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -70,6 +70,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.DumpableCollection; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1748,6 +1749,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio */ private class StreamCreator { + private final AutoLock lock = new AutoLock(); private final Queue slots = new ArrayDeque<>(); private Thread flushing; @@ -1825,7 +1827,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { if (streamId <= 0) { - synchronized (this) + try (AutoLock l = lock.lock()) { streamId = localStreamIds.getAndAdd(2); slots.offer(slot); @@ -1833,7 +1835,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } else { - synchronized (this) + try (AutoLock l = lock.lock()) { slots.offer(slot); } @@ -1843,7 +1845,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio private void releaseSlotFlushAndFail(Slot slot, Promise promise, Throwable x) { - synchronized (this) + try (AutoLock l = lock.lock()) { slots.remove(slot); } @@ -1870,7 +1872,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio while (true) { ControlEntry entry; - synchronized (this) + try (AutoLock l = lock.lock()) { if (flushing == null) flushing = thread; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index e13c7352e49..d31d716d66e 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -143,7 +143,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public void reset(ResetFrame frame, Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { if (isReset()) return; @@ -156,7 +156,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private boolean startWrite(Callback callback) { Throwable failure; - synchronized (this) + try (AutoLock l = lock.lock()) { failure = this.failure; if (failure == null && sendCallback == null) @@ -192,7 +192,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public boolean isReset() { - synchronized (this) + try (AutoLock l = lock.lock()) { return localReset || remoteReset; } @@ -200,7 +200,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private boolean isFailed() { - synchronized (this) + try (AutoLock l = lock.lock()) { return failure != null; } @@ -209,7 +209,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa @Override public boolean isResetOrFailed() { - synchronized (this) + try (AutoLock l = lock.lock()) { return isReset() || isFailed(); } @@ -476,7 +476,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private void onReset(ResetFrame frame, Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { remoteReset = true; failure = new EofException("reset"); @@ -501,7 +501,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private void onFailure(FailureFrame frame, Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { failure = frame.getFailure(); } @@ -689,7 +689,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa private Callback endWrite() { - synchronized (this) + try (AutoLock l = lock.lock()) { Callback callback = sendCallback; sendCallback = null; diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java index 5c3e5bbc19e..0251f720672 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java @@ -35,6 +35,7 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +44,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint { private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class); + private final AutoLock lock = new AutoLock(); private final Deque dataQueue = new ArrayDeque<>(); private final AtomicReference writeState = new AtomicReference<>(WriteState.IDLE); private final AtomicReference readCallback = new AtomicReference<>(); @@ -172,7 +174,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint public int fill(ByteBuffer sink) throws IOException { Entry entry; - synchronized (this) + try (AutoLock l = lock.lock()) { entry = dataQueue.poll(); } @@ -206,7 +208,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint if (source.hasRemaining()) { - synchronized (this) + try (AutoLock l = lock.lock()) { dataQueue.offerFirst(entry); } @@ -548,7 +550,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint private void offer(ByteBuffer buffer, Callback callback, Throwable failure) { - synchronized (this) + try (AutoLock l = lock.lock()) { dataQueue.offer(new Entry(buffer, callback, failure)); } @@ -557,7 +559,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint protected void process() { boolean empty; - synchronized (this) + try (AutoLock l = lock.lock()) { empty = dataQueue.isEmpty(); } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java index a2f58724ca0..3747150c354 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java @@ -22,6 +22,7 @@ import java.io.Closeable; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.Frame; +import org.eclipse.jetty.util.Attachable; import org.eclipse.jetty.util.Callback; /** @@ -29,21 +30,8 @@ import org.eclipse.jetty.util.Callback; *

This class extends {@link Stream} by adding the methods required to * implement the HTTP/2 stream functionalities.

*/ -public interface IStream extends Stream, Closeable +public interface IStream extends Stream, Attachable, Closeable { - /** - * @return the object attached to this stream - * @see #setAttachment(Object) - */ - public Object getAttachment(); - - /** - * Attaches the given object to this stream for later retrieval. - * - * @param attachment the object to attach to this stream - */ - public void setAttachment(Object attachment); - /** * @return whether this stream is local or remote */ diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java index 7ebf5ea9cea..c70d75da2c3 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java @@ -34,9 +34,13 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HttpChannelOverHTTP2 extends HttpChannel { + private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHTTP2.class); + private final Stream.Listener listener = new Listener(); private final HttpConnectionOverHTTP2 connection; private final Session session; diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java index e2cfc1e8650..b55924a5dfb 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java @@ -50,6 +50,7 @@ import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -246,6 +247,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. private class ContentNotifier { + private final AutoLock lock = new AutoLock(); private final Queue queue = new ArrayDeque<>(); private final HttpReceiverOverHTTP2 receiver; private DataInfo dataInfo; @@ -269,7 +271,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. private void enqueue(DataInfo dataInfo) { - synchronized (this) + try (AutoLock l = lock.lock()) { queue.offer(dataInfo); } @@ -285,7 +287,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. return; // Process only if there is demand. - synchronized (this) + try (AutoLock l = lock.lock()) { if (!resume && demand() <= 0) { @@ -309,7 +311,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. } } - synchronized (this) + try (AutoLock l = lock.lock()) { dataInfo = queue.poll(); if (LOG.isDebugEnabled()) @@ -347,7 +349,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. private boolean active(boolean resume) { - synchronized (this) + try (AutoLock l = lock.lock()) { if (active) { @@ -380,7 +382,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. */ private boolean stall() { - synchronized (this) + try (AutoLock l = lock.lock()) { if (resume) { @@ -400,7 +402,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel. private void reset() { dataInfo = null; - synchronized (this) + try (AutoLock l = lock.lock()) { queue.clear(); active = false; diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java index efce84ed39e..9bcf82d4bd0 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java @@ -58,6 +58,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.thread.AutoLock; public class HTTP2ServerConnection extends HTTP2Connection { @@ -84,7 +85,8 @@ public class HTTP2ServerConnection extends HTTP2Connection return false; } } - + + private final AutoLock lock = new AutoLock(); private final Queue channels = new ArrayDeque<>(); private final List upgradeFrames = new ArrayList<>(); private final AtomicLong totalRequests = new AtomicLong(); @@ -287,7 +289,7 @@ public class HTTP2ServerConnection extends HTTP2Connection { if (isRecycleHttpChannels()) { - synchronized (this) + try (AutoLock l = lock.lock()) { channels.offer(channel); } @@ -298,7 +300,7 @@ public class HTTP2ServerConnection extends HTTP2Connection { if (isRecycleHttpChannels()) { - synchronized (this) + try (AutoLock l = lock.lock()) { return channels.poll(); } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index e08993de746..b12a47d470d 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -413,6 +414,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport */ private class TransportCallback implements Callback { + private final AutoLock _lock = new AutoLock(); private State _state = State.IDLE; private Callback _callback; private boolean _commit; @@ -420,7 +422,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport private void reset(Throwable failure) { - assert Thread.holdsLock(this); + assert _lock.isHeldByCurrentThread(); _state = failure != null ? State.FAILED : State.IDLE; _callback = null; _commit = false; @@ -443,7 +445,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport private Throwable sending(Callback callback, boolean commit) { - synchronized (this) + try (AutoLock l = _lock.lock()) { switch (_state) { @@ -471,7 +473,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport { Callback callback; boolean commit; - synchronized (this) + try (AutoLock l = _lock.lock()) { if (_state != State.SENDING) { @@ -495,7 +497,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport { Callback callback; boolean commit; - synchronized (this) + try (AutoLock l = _lock.lock()) { if (_state != State.SENDING) { @@ -517,7 +519,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport private boolean idleTimeout(Throwable failure) { Callback callback = null; - synchronized (this) + try (AutoLock l = _lock.lock()) { // Ignore idle timeouts if not writing, // as the application may be suspended. @@ -542,7 +544,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport public InvocationType getInvocationType() { Callback callback; - synchronized (this) + try (AutoLock l = _lock.lock()) { callback = _callback; } 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 bddd34d0bfc..c360152cf1c 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 @@ -127,7 +127,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void doShutdownOutput() { super.doShutdownOutput(); - try (AutoLock lock = _lock.lock()) + try (AutoLock l = _lock.lock()) { _hasOutput.signalAll(); } @@ -137,7 +137,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public void doClose() { super.doClose(); - try (AutoLock lock = _lock.lock()) + try (AutoLock l = _lock.lock()) { _hasOutput.signalAll(); } @@ -303,7 +303,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint { ByteBuffer b; - try (AutoLock lock = _lock.lock()) + try (AutoLock l = _lock.lock()) { while (BufferUtil.isEmpty(_out) && !isOutputShutdown()) { @@ -401,7 +401,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint public boolean flush(ByteBuffer... buffers) throws IOException { boolean flushed = true; - try (AutoLock lock = _lock.lock()) + try (AutoLock l = _lock.lock()) { if (!isOpen()) throw new IOException("CLOSED"); @@ -447,7 +447,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint @Override public void reset() { - try (AutoLock lock = _lock.lock()) + try (AutoLock l = _lock.lock()) { _inQ.clear(); _hasOutput.signalAll(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/IncludeExcludeConnectionStatistics.java b/jetty-io/src/main/java/org/eclipse/jetty/io/IncludeExcludeConnectionStatistics.java new file mode 100644 index 00000000000..04e5f34847c --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/IncludeExcludeConnectionStatistics.java @@ -0,0 +1,114 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.io; + +import java.util.AbstractSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Predicate; + +import org.eclipse.jetty.util.IncludeExcludeSet; + +public class IncludeExcludeConnectionStatistics extends ConnectionStatistics +{ + private final IncludeExcludeSet, Connection> _set = new IncludeExcludeSet<>(ConnectionSet.class); + + public void include(String className) throws ClassNotFoundException + { + _set.include(connectionForName(className)); + } + + public void include(Class clazz) + { + _set.include(clazz); + } + + public void exclude(String className) throws ClassNotFoundException + { + _set.exclude(connectionForName(className)); + } + + public void exclude(Class clazz) + { + _set.exclude(clazz); + } + + private Class connectionForName(String className) throws ClassNotFoundException + { + Class aClass = Class.forName(className); + if (!Connection.class.isAssignableFrom(aClass)) + throw new IllegalArgumentException("Class is not a Connection"); + + @SuppressWarnings("unchecked") + Class connectionClass = (Class)aClass; + return connectionClass; + } + + @Override + public void onOpened(Connection connection) + { + if (_set.test(connection)) + super.onOpened(connection); + } + + @Override + public void onClosed(Connection connection) + { + if (_set.test(connection)) + super.onClosed(connection); + } + + public static class ConnectionSet extends AbstractSet> implements Predicate + { + private final Set> set = new HashSet<>(); + + @Override + public boolean add(Class aClass) + { + return set.add(aClass); + } + + @Override + public boolean remove(Object o) + { + return set.remove(o); + } + + @Override + public Iterator> iterator() + { + return set.iterator(); + } + + @Override + public int size() + { + return set.size(); + } + + @Override + public boolean test(Connection connection) + { + if (connection == null) + return false; + return set.stream().anyMatch(c -> c.isAssignableFrom(connection.getClass())); + } + } +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java index 47651f816a0..58c7f058bfd 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java @@ -48,6 +48,7 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill; @@ -79,6 +80,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable } } + private final AutoLock _lock = new AutoLock(); private final AtomicBoolean _started = new AtomicBoolean(false); private boolean _selecting; private final SelectorManager _selectorManager; @@ -252,7 +254,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable LOG.debug("Queued change lazy={} {} on {}", lazy, update, this); Selector selector = null; - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { _updates.offer(update); @@ -278,7 +280,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable LOG.debug("Wakeup {}", this); Selector selector = null; - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { if (_selecting) { @@ -382,7 +384,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private int getActionSize() { - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { return _updates.size(); } @@ -424,7 +426,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { DumpKeys dump = new DumpKeys(); String updatesAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()); - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { updates = new ArrayList<>(_updates); _updates.addFirst(dump); @@ -514,7 +516,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable private void processUpdates() { - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { Deque updates = _updates; _updates = _updateable; @@ -543,7 +545,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable Selector selector; int updates; - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { updates = _updates.size(); _selecting = updates == 0; @@ -579,7 +581,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size()); int updates; - synchronized (ManagedSelector.this) + try (AutoLock l = _lock.lock()) { // finished selecting _selecting = false; @@ -887,7 +889,11 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { this.channel = channel; this.attachment = attachment; - this.timeout = ManagedSelector.this._selectorManager.getScheduler().schedule(this, ManagedSelector.this._selectorManager.getConnectTimeout(), TimeUnit.MILLISECONDS); + long timeout = ManagedSelector.this._selectorManager.getConnectTimeout(); + if (timeout > 0) + this.timeout = ManagedSelector.this._selectorManager.getScheduler().schedule(this, timeout, TimeUnit.MILLISECONDS); + else + this.timeout = null; } @Override @@ -918,7 +924,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable { if (failed.compareAndSet(false, true)) { - timeout.cancel(); + if (timeout != null) + timeout.cancel(); IO.close(channel); ManagedSelector.this._selectorManager.connectionFailed(channel, failure, attachment); } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java index e662c104b58..67b2141b048 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java @@ -29,6 +29,7 @@ import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Scheduler; import org.slf4j.Logger; @@ -42,6 +43,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe { private static final Logger LOG = LoggerFactory.getLogger(SocketChannelEndPoint.class); + private final AutoLock _lock = new AutoLock(); private final SocketChannel _channel; private final ManagedSelector _selector; private SelectionKey _key; @@ -317,7 +319,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe int readyOps = _key.readyOps(); int oldInterestOps; int newInterestOps; - synchronized (this) + try (AutoLock l = _lock.lock()) { _updatePending = true; // Remove the readyOps, that here can only be OP_READ or OP_WRITE (or both). @@ -361,7 +363,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe { int oldInterestOps; int newInterestOps; - synchronized (this) + try (AutoLock l = _lock.lock()) { _updatePending = false; oldInterestOps = _currentInterestOps; @@ -403,7 +405,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe int oldInterestOps; int newInterestOps; boolean pending; - synchronized (this) + try (AutoLock l = _lock.lock()) { pending = _updatePending; oldInterestOps = _desiredInterestOps; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java index 589c34d7c62..a78b0e863f0 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java @@ -33,7 +33,6 @@ public class WriterOutputStream extends OutputStream { protected final Writer _writer; protected final Charset _encoding; - private final byte[] _buf = new byte[1]; public WriterOutputStream(Writer writer, String encoding) { @@ -82,11 +81,9 @@ public class WriterOutputStream extends OutputStream } @Override - public synchronized void write(int b) + public void write(int b) throws IOException { - _buf[0] = (byte)b; - write(_buf); + write(new byte[]{(byte)b}); } } - diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 25caea62ad8..8c1cb69e476 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,6 +105,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr WAIT_FOR_FILL // Waiting for a fill to happen } + private final AutoLock _lock = new AutoLock(); private final List handshakeListeners = new ArrayList<>(); private final ByteBufferPool _bufferPool; private final SSLEngine _sslEngine; @@ -435,7 +437,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr private void releaseEncryptedOutputBuffer() { - if (!Thread.holdsLock(_decryptedEndPoint)) + if (!_lock.isHeldByCurrentThread()) throw new IllegalStateException(); if (_encryptedOutput != null && !_encryptedOutput.hasRemaining()) { @@ -514,7 +516,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read boolean waitingForFill; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug("onFillable {}", SslConnection.this); @@ -527,7 +529,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr if (waitingForFill) { - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { waitingForFill = _flushState == FlushState.WAIT_FOR_FILL; } @@ -545,7 +547,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read boolean fail = false; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug("onFillableFail {}", SslConnection.this, failure); @@ -594,7 +596,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { try { - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug(">fill {}", SslConnection.this); @@ -814,7 +816,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr boolean fillable; ByteBuffer write = null; boolean interest = false; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug(">needFillInterest s={}/{} uf={} ei={} di={} {}", @@ -958,7 +960,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { try { - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) { @@ -1148,7 +1150,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { boolean fillInterest = false; ByteBuffer write = null; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug(">onIncompleteFlush {} {}", SslConnection.this, BufferUtil.toDetailString(_encryptedOutput)); @@ -1242,7 +1244,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { boolean close; boolean flush = false; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { boolean ishut = endPoint.isInputShutdown(); boolean oshut = endPoint.isOutputShutdown(); @@ -1268,7 +1270,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr // If we still can't flush, but we are not closing the endpoint, // let's just flush the encrypted output in the background. ByteBuffer write = null; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (BufferUtil.hasContent(_encryptedOutput)) { @@ -1280,7 +1282,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { endPoint.write(Callback.from(() -> { - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { _flushState = FlushState.IDLE; releaseEncryptedOutputBuffer(); @@ -1455,7 +1457,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr private Throwable handleException(Throwable x, String context) { - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (_failure == null) { @@ -1497,7 +1499,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { boolean fillable; boolean interested; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug("IncompleteWriteCB succeeded {}", SslConnection.this); @@ -1522,7 +1524,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr public void failed(final Throwable x) { boolean failFillInterest; - synchronized (_decryptedEndPoint) + try (AutoLock l = _lock.lock()) { if (LOG.isDebugEnabled()) LOG.debug("IncompleteWriteCB failed {}", SslConnection.this, x); diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java index 3ad5bdd737e..1087c4b975e 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java @@ -214,7 +214,7 @@ public class SslConnectionTest } @Override - public synchronized void onFillable() + public void onFillable() { EndPoint endp = getEndPoint(); try diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java index 5d47d117d70..6b2a9a64e43 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.util.thread.AutoLock; /** * UserInfo @@ -34,7 +35,7 @@ import org.eclipse.jetty.util.security.Credential; */ public class UserInfo { - + private final AutoLock _lock = new AutoLock(); private String _userName; private Credential _credential; protected List _roleNames = new ArrayList<>(); @@ -80,7 +81,7 @@ public class UserInfo public void fetchRoles() throws Exception { - synchronized (_roleNames) + try (AutoLock l = _lock.lock()) { if (!_rolesLoaded) { diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java index 96a9a1f2ba0..62edd1d6237 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.jndi; import java.io.IOException; -import java.util.Collections; import java.util.Hashtable; import java.util.Map; import java.util.WeakHashMap; @@ -32,6 +31,7 @@ import javax.naming.spi.ObjectFactory; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +63,7 @@ public class ContextFactory implements ObjectFactory /** * Map of classloaders to contexts. */ - private static final Map __contextMap = Collections.synchronizedMap(new WeakHashMap<>()); + private static final Map __contextMap = new WeakHashMap<>(); /** * Threadlocal for injecting a context to use @@ -77,6 +77,8 @@ public class ContextFactory implements ObjectFactory */ private static final ThreadLocal __threadClassLoader = new ThreadLocal(); + private static final AutoLock __lock = new AutoLock(); + /** * Find or create a context which pertains to a classloader. *

@@ -116,7 +118,7 @@ public class ContextFactory implements ObjectFactory { if (LOG.isDebugEnabled()) LOG.debug("Using threadlocal classloader"); - synchronized (__contextMap) + try (AutoLock l = __lock.lock()) { ctx = getContextForClassLoader(loader); if (ctx == null) @@ -137,7 +139,7 @@ public class ContextFactory implements ObjectFactory { if (LOG.isDebugEnabled()) LOG.debug("Trying thread context classloader"); - synchronized (__contextMap) + try (AutoLock l = __lock.lock()) { while (ctx == null && loader != null) { @@ -163,7 +165,7 @@ public class ContextFactory implements ObjectFactory { if (LOG.isDebugEnabled() && loader != null) LOG.debug("Trying classloader of current org.eclipse.jetty.server.handler.ContextHandler"); - synchronized (__contextMap) + try (AutoLock l = __lock.lock()) { loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader(); ctx = (Context)__contextMap.get(loader); @@ -220,8 +222,10 @@ public class ContextFactory implements ObjectFactory { if (loader == null) return null; - - return (Context)__contextMap.get(loader); + try (AutoLock l = __lock.lock()) + { + return __contextMap.get(loader); + } } /** @@ -257,7 +261,7 @@ public class ContextFactory implements ObjectFactory public static void dump(Appendable out, String indent) throws IOException { - synchronized (__contextMap) + try (AutoLock l = __lock.lock()) { Dumpable.dumpObjects(out, indent, String.format("o.e.j.jndi.ContextFactory@%x", __contextMap.hashCode()), __contextMap); } diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml index af03a0f479c..cb720639c2b 100644 --- a/jetty-nosql/pom.xml +++ b/jetty-nosql/pom.xml @@ -31,7 +31,6 @@ org.mongodb mongo-java-driver 2.13.2 - jar compile diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java index 8b1b829d7ce..e5b2fd33bb6 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java @@ -278,7 +278,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator } // Attempt to login with the provided authCode - OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration); + OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request)); UserIdentity user = login(null, credentials, request); if (user != null) { diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java index a6650addae3..a2a26fb2887 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.FormRequestContent; @@ -38,7 +37,7 @@ import org.slf4j.LoggerFactory; * *

* This is constructed with an authorization code from the authentication request. This authorization code - * is then exchanged using {@link #redeemAuthCode(HttpClient)} for a response containing the ID Token and Access Token. + * is then exchanged using {@link #redeemAuthCode(OpenIdConfiguration)} for a response containing the ID Token and Access Token. * The response is then validated against the {@link OpenIdConfiguration}. *

*/ @@ -48,16 +47,14 @@ public class OpenIdCredentials implements Serializable private static final long serialVersionUID = 4766053233370044796L; private final String redirectUri; - private final OpenIdConfiguration configuration; private String authCode; private Map response; private Map claims; - public OpenIdCredentials(String authCode, String redirectUri, OpenIdConfiguration configuration) + public OpenIdCredentials(String authCode, String redirectUri) { this.authCode = authCode; this.redirectUri = redirectUri; - this.configuration = configuration; } public String getUserId() @@ -75,7 +72,7 @@ public class OpenIdCredentials implements Serializable return response; } - public void redeemAuthCode(HttpClient httpClient) throws Exception + public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception { if (LOG.isDebugEnabled()) LOG.debug("redeemAuthCode() {}", this); @@ -84,26 +81,26 @@ public class OpenIdCredentials implements Serializable { try { - response = claimAuthCode(httpClient, authCode); + response = claimAuthCode(configuration); if (LOG.isDebugEnabled()) LOG.debug("response: {}", response); String idToken = (String)response.get("id_token"); if (idToken == null) - throw new IllegalArgumentException("no id_token"); + throw new AuthenticationException("no id_token"); String accessToken = (String)response.get("access_token"); if (accessToken == null) - throw new IllegalArgumentException("no access_token"); + throw new AuthenticationException("no access_token"); String tokenType = (String)response.get("token_type"); if (!"Bearer".equalsIgnoreCase(tokenType)) - throw new IllegalArgumentException("invalid token_type"); + throw new AuthenticationException("invalid token_type"); claims = JwtDecoder.decode(idToken); if (LOG.isDebugEnabled()) LOG.debug("claims {}", claims); - validateClaims(); + validateClaims(configuration); } finally { @@ -113,22 +110,28 @@ public class OpenIdCredentials implements Serializable } } - private void validateClaims() + private void validateClaims(OpenIdConfiguration configuration) throws Exception { // Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim. if (!configuration.getIssuer().equals(claims.get("iss"))) - throw new IllegalArgumentException("Issuer Identifier MUST exactly match the iss Claim"); + throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim"); // The aud (audience) Claim MUST contain the client_id value. - validateAudience(); + validateAudience(configuration); // If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value. Object azp = claims.get("azp"); if (azp != null && !configuration.getClientId().equals(azp)) - throw new IllegalArgumentException("Authorized party claim value should be the client_id"); + throw new AuthenticationException("Authorized party claim value should be the client_id"); + + // Check that the ID token has not expired by checking the exp claim. + long expiry = (Long)claims.get("exp"); + long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F); + if (currentTimeSeconds > expiry) + throw new AuthenticationException("ID Token has expired"); } - private void validateAudience() + private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException { Object aud = claims.get("aud"); String clientId = configuration.getClientId(); @@ -137,38 +140,21 @@ public class OpenIdCredentials implements Serializable boolean isValidType = isString || isList; if (isString && !clientId.equals(aud)) - throw new IllegalArgumentException("Audience Claim MUST contain the client_id value"); + throw new AuthenticationException("Audience Claim MUST contain the client_id value"); else if (isList) { if (!Arrays.asList((Object[])aud).contains(clientId)) - throw new IllegalArgumentException("Audience Claim MUST contain the client_id value"); + throw new AuthenticationException("Audience Claim MUST contain the client_id value"); if (claims.get("azp") == null) - throw new IllegalArgumentException("A multi-audience ID token needs to contain an azp claim"); + throw new AuthenticationException("A multi-audience ID token needs to contain an azp claim"); } else if (!isValidType) - throw new IllegalArgumentException("Audience claim was not valid"); + throw new AuthenticationException("Audience claim was not valid"); } - public boolean isExpired() - { - if (authCode != null || claims == null) - return true; - - // Check expiry - long expiry = (Long)claims.get("exp"); - long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F); - if (currentTimeSeconds > expiry) - { - if (LOG.isDebugEnabled()) - LOG.debug("OpenId Credentials expired {}", this); - return true; - } - - return false; - } - - private Map claimAuthCode(HttpClient httpClient, String authCode) throws Exception + @SuppressWarnings("unchecked") + private Map claimAuthCode(OpenIdConfiguration configuration) throws Exception { Fields fields = new Fields(); fields.add("code", authCode); @@ -177,7 +163,7 @@ public class OpenIdCredentials implements Serializable fields.add("redirect_uri", redirectUri); fields.add("grant_type", "authorization_code"); FormRequestContent formContent = new FormRequestContent(fields); - Request request = httpClient.POST(configuration.getTokenEndpoint()) + Request request = configuration.getHttpClient().POST(configuration.getTokenEndpoint()) .body(formContent) .timeout(10, TimeUnit.SECONDS); ContentResponse response = request.send(); @@ -187,7 +173,15 @@ public class OpenIdCredentials implements Serializable Object parsedResponse = new JSON().fromJSON(responseBody); if (!(parsedResponse instanceof Map)) - throw new IllegalStateException("Malformed response from OpenID Provider"); + throw new AuthenticationException("Malformed response from OpenID Provider"); return (Map)parsedResponse; } + + public static class AuthenticationException extends Exception + { + public AuthenticationException(String message) + { + super(message); + } + } } diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java index bfded720934..fa0db1d2cb7 100644 --- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java +++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java @@ -18,11 +18,9 @@ package org.eclipse.jetty.security.openid; -import java.security.Principal; import javax.security.auth.Subject; import javax.servlet.ServletRequest; -import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.server.UserIdentity; @@ -43,7 +41,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi private final OpenIdConfiguration configuration; private final LoginService loginService; - private final HttpClient httpClient; private IdentityService identityService; private boolean authenticateNewUsers; @@ -63,7 +60,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi { this.configuration = configuration; this.loginService = loginService; - this.httpClient = configuration.getHttpClient(); addBean(this.configuration); addBean(this.loginService); } @@ -88,9 +84,7 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi OpenIdCredentials openIdCredentials = (OpenIdCredentials)credentials; try { - openIdCredentials.redeemAuthCode(httpClient); - if (openIdCredentials.isExpired()) - return null; + openIdCredentials.redeemAuthCode(configuration); } catch (Throwable e) { @@ -141,12 +135,10 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi @Override public boolean validate(UserIdentity user) { - Principal userPrincipal = user.getUserPrincipal(); - if (!(userPrincipal instanceof OpenIdUserPrincipal)) + if (!(user.getUserPrincipal() instanceof OpenIdUserPrincipal)) return false; - OpenIdCredentials credentials = ((OpenIdUserPrincipal)userPrincipal).getCredentials(); - return !credentials.isExpired(); + return loginService == null || loginService.validate(user); } @Override @@ -170,5 +162,7 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi @Override public void logout(UserIdentity user) { + if (loginService != null) + loginService.logout(user); } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java index 6d68c9a4de6..b4e4c9c554b 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java @@ -129,9 +129,11 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe public Enumeration getResources(String name) throws IOException { Enumeration osgiUrls = _osgiBundleClassLoader.getResources(name); + if (osgiUrls != null && osgiUrls.hasMoreElements()) + return osgiUrls; + Enumeration urls = super.getResources(name); - List resources = toList(osgiUrls, urls); - return Collections.enumeration(resources); + return urls; } @Override diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java index 256e4e5aa4f..67d2300ead3 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java @@ -96,37 +96,40 @@ public class OSGiClassLoader extends URLClassLoader } @Override - protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - Class c = findLoadedClass(name); - ClassNotFoundException ex = null; - boolean triedParent = false; - - if (c == null) + synchronized (getClassLoadingLock(name)) { - try + Class c = findLoadedClass(name); + ClassNotFoundException ex = null; + boolean triedParent = false; + + if (c == null) { - c = this.findClass(name); - } - catch (ClassNotFoundException e) - { - ex = e; + try + { + c = this.findClass(name); + } + catch (ClassNotFoundException e) + { + ex = e; + } } + + if (c == null && _parent != null && !triedParent) + c = _parent.loadClass(name); + + if (c == null) + throw ex; + + if (resolve) + resolveClass(c); + + if (LOG.isDebugEnabled()) + LOG.debug("loaded " + c + " from " + c.getClassLoader()); + + return c; } - - if (c == null && _parent != null && !triedParent) - c = _parent.loadClass(name); - - if (c == null) - throw ex; - - if (resolve) - resolveClass(c); - - if (LOG.isDebugEnabled()) - LOG.debug("loaded " + c + " from " + c.getClassLoader()); - - return c; } @Override diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml index ecc03573a34..a854e1d77e6 100644 --- a/jetty-osgi/pom.xml +++ b/jetty-osgi/pom.xml @@ -24,6 +24,7 @@ jetty-osgi-boot-warurl jetty-osgi-httpservice test-jetty-osgi-webapp + test-jetty-osgi-webapp-resources test-jetty-osgi-context test-jetty-osgi-fragment test-jetty-osgi-server diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml new file mode 100644 index 00000000000..303c10a2c19 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp-resources/pom.xml @@ -0,0 +1,91 @@ + + + + org.eclipse.jetty.osgi + jetty-osgi-project + 10.0.0-SNAPSHOT + + 4.0.0 + test-jetty-osgi-webapp-resources + OSGi Test :: Webapp With Resources + http://www.eclipse.org/jetty + war + + ${project.groupId}.webapp.resources + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + true + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-resources + validate + + copy-resources + + + ${basedir}/target/classes + + + src/main/resources + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + war + + + !com.acme* + / + + + + + + maven-war-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + false + + + + + + + org.eclipse.jetty.toolchain + jetty-servlet-api + provided + + + diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/java/com/acme/HelloWorld.java b/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/java/com/acme/HelloWorld.java new file mode 100644 index 00000000000..146ba1466e9 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/java/com/acme/HelloWorld.java @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package com.acme; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Set; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Dump Servlet Request. + */ +@SuppressWarnings("serial") +public class HelloWorld extends HttpServlet +{ + + @Override + public void init(ServletConfig config) throws ServletException + { + super.init(config); + } + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + doGet(request, response); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("text/html"); + ServletOutputStream out = response.getOutputStream(); + out.println(""); + out.println("

Hello World

"); + + Enumeration resources = Thread.currentThread().getContextClassLoader().getResources("fake.properties"); + + while (resources.hasMoreElements()) + out.println(resources.nextElement().toString()); + + out.println(""); + out.flush(); + } +} diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/resources/fake.properties b/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/resources/fake.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/webapp/WEB-INF/web.xml b/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..90efb1dceac --- /dev/null +++ b/jetty-osgi/test-jetty-osgi-webapp-resources/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + + WebApp With Resources + + + Hello + com.acme.HelloWorld + 1 + + + + Hello + /hello/* + + + + + diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 0983f62033b..e7de70866d4 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -38,6 +38,12 @@ pax-exam-container-forked ${pax.exam.version} test + + + biz.aQute.bnd + bndlib + + org.ops4j.pax.swissbox @@ -69,17 +75,6 @@ ${pax.url.version} test - - org.ops4j.pax.tinybundles - tinybundles - 3.0.0 - - - biz.aQute.bnd - bnd - - - org.ops4j.pax.url pax-url-wrap @@ -95,7 +90,7 @@ biz.aQute.bnd biz.aQute.bndlib - 5.0.0 + 5.1.2 org.osgi @@ -393,6 +388,13 @@ ${project.version} test + + org.eclipse.jetty.osgi + test-jetty-osgi-webapp-resources + ${project.version} + war + test + org.eclipse.jetty.osgi test-jetty-osgi-fragment @@ -550,6 +552,23 @@ + + maven-dependency-plugin + + + copy + process-test-resources + + copy-dependencies + + + + + test-jetty-osgi-webapp-resources + target + true + + org.apache.servicemix.tooling depends-maven-plugin diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-http-boot-with-resources.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-http-boot-with-resources.xml new file mode 100644 index 00000000000..1030e0d3f85 --- /dev/null +++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-http-boot-with-resources.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + boot.resources.port + + + + + + + + + + + diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java index 9ce67297315..73291ccd87d 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java @@ -92,7 +92,6 @@ public class TestJettyOSGiBootWithJavaxWebSocket { List diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java index b38d3779863..fb530a6b384 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxServerUpgradeRequest.java @@ -22,13 +22,13 @@ import java.net.URI; import java.security.Principal; import org.eclipse.jetty.websocket.javax.common.UpgradeRequest; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; public class JavaxServerUpgradeRequest implements UpgradeRequest { - private final ServletUpgradeRequest servletRequest; + private final ServerUpgradeRequest servletRequest; - public JavaxServerUpgradeRequest(ServletUpgradeRequest servletRequest) + public JavaxServerUpgradeRequest(ServerUpgradeRequest servletRequest) { this.servletRequest = servletRequest; } diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java index 4a1387c86a6..b8c950a9ebf 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java @@ -38,8 +38,8 @@ import org.eclipse.jetty.websocket.javax.common.ConfiguredEndpoint; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketExtension; import org.eclipse.jetty.websocket.javax.common.ServerEndpointConfigWrapper; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; import org.eclipse.jetty.websocket.util.server.internal.WebSocketCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +62,7 @@ public class JavaxWebSocketCreator implements WebSocketCreator } @Override - public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp) { final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(req); final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(resp); diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java index 4f6c29a38f4..e9e69a3a1cf 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.websocket.javax.server.internal; -import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.server.ServerEndpoint; @@ -28,8 +27,8 @@ import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientFra import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClientFrameHandlerFactory implements FrameHandlerFactory { @@ -42,7 +41,7 @@ public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClien public JavaxWebSocketFrameHandlerMetadata getMetadata(Class endpointClass, EndpointConfig endpointConfig) { if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass)) - return createEndpointMetadata((Class)endpointClass, endpointConfig); + return createEndpointMetadata(endpointConfig); ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class); if (anno == null) @@ -55,7 +54,7 @@ public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClien } @Override - public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse) + public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse) { return newJavaxWebSocketFrameHandler(websocketPojo, new JavaxServerUpgradeRequest(upgradeRequest)); } diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java index dbb8e1f1fb3..280cecd27e4 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeRequest.java @@ -25,13 +25,13 @@ import java.util.Map; import javax.websocket.server.HandshakeRequest; import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; public class JsrHandshakeRequest implements HandshakeRequest { - private final ServletUpgradeRequest delegate; + private final ServerUpgradeRequest delegate; - public JsrHandshakeRequest(ServletUpgradeRequest req) + public JsrHandshakeRequest(ServerUpgradeRequest req) { this.delegate = req; } diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java index 80ec6a7502b..b55ec99f3fd 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JsrHandshakeResponse.java @@ -23,14 +23,14 @@ import java.util.List; import java.util.Map; import javax.websocket.HandshakeResponse; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; public class JsrHandshakeResponse implements HandshakeResponse { - private final ServletUpgradeResponse delegate; + private final ServerUpgradeResponse delegate; private Map> headerMap; - public JsrHandshakeResponse(ServletUpgradeResponse resp) + public JsrHandshakeResponse(ServerUpgradeResponse resp) { this.delegate = resp; this.headerMap = new HashMap<>(); diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EchoSocket.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EchoSocket.java new file mode 100644 index 00000000000..c18d8b665a4 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EchoSocket.java @@ -0,0 +1,43 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests; + +import java.io.IOException; +import java.nio.ByteBuffer; +import javax.websocket.ClientEndpoint; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/") +@ClientEndpoint +public class EchoSocket extends EventSocket +{ + @Override + public void onMessage(String message) throws IOException + { + super.onMessage(message); + session.getBasicRemote().sendText(message); + } + + @Override + public void onMessage(ByteBuffer message) throws IOException + { + super.onMessage(message); + session.getBasicRemote().sendBinary(message); + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java index a8b7d1a5fe9..a69db90ba45 100644 --- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java @@ -98,23 +98,4 @@ public class EventSocket error = cause; errorLatch.countDown(); } - - @ServerEndpoint("/") - @ClientEndpoint - public static class EchoSocket extends EventSocket - { - @Override - public void onMessage(String message) throws IOException - { - super.onMessage(message); - session.getBasicRemote().sendText(message); - } - - @Override - public void onMessage(ByteBuffer message) throws IOException - { - super.onMessage(message); - session.getBasicRemote().sendBinary(message); - } - } } diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java index f1be31e72e4..4b8f76c4f3a 100644 --- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/NetworkFuzzer.java @@ -39,7 +39,7 @@ import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.FrameHandler; -import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.internal.Generator; @@ -179,7 +179,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab } } - public static class RawUpgradeRequest extends ClientUpgradeRequest + public static class RawUpgradeRequest extends CoreClientUpgradeRequest { private final FrameCapture frameCapture = new FrameCapture(); private final CompletableFuture futureCapture; diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/GracefulCloseTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/GracefulCloseTest.java new file mode 100644 index 00000000000..b2745345484 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/GracefulCloseTest.java @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests; + +import java.net.URI; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.websocket.CloseReason; +import javax.websocket.EndpointConfig; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.component.Graceful; +import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GracefulCloseTest +{ + private static final BlockingArrayQueue serverEndpoints = new BlockingArrayQueue<>(); + private Server server; + private URI serverUri; + private JavaxWebSocketClientContainer client; + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + server.setHandler(contextHandler); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addEndpoint(ServerSocket.class)); + server.start(); + serverUri = WSURI.toWebsocket(server.getURI()); + + // StopTimeout is necessary for the websocket server sessions to gracefully close. + server.setStopTimeout(1000); + + client = new JavaxWebSocketClientContainer(); + client.start(); + } + + @AfterEach + public void after() throws Exception + { + client.stop(); + server.stop(); + } + + @ServerEndpoint("/") + public static class ServerSocket extends EchoSocket + { + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) + { + serverEndpoints.add(this); + super.onOpen(session, endpointConfig); + } + } + + @Test + public void testClientStop() throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connectToServer(clientEndpoint, serverUri); + EventSocket serverEndpoint = Objects.requireNonNull(serverEndpoints.poll(5, TimeUnit.SECONDS)); + + // There is no API for a Javax WebSocketContainer stop timeout. + Graceful.shutdown(client).get(5, TimeUnit.SECONDS); + client.stop(); + + // Check that the client endpoint was closed with the correct status code and no error. + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.GOING_AWAY)); + assertNull(clientEndpoint.error); + + // Check that the server endpoint was closed with the correct status code and no error. + assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(serverEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.GOING_AWAY)); + assertNull(serverEndpoint.error); + } + + @Test + public void testServerStop() throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connectToServer(clientEndpoint, serverUri); + EventSocket serverEndpoint = Objects.requireNonNull(serverEndpoints.poll(5, TimeUnit.SECONDS)); + + server.stop(); + + // Check that the client endpoint was closed with the correct status code and no error. + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.GOING_AWAY)); + assertNull(clientEndpoint.error); + + // Check that the server endpoint was closed with the correct status code and no error. + assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(serverEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.GOING_AWAY)); + assertNull(serverEndpoint.error); + } +} diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java index 6b02668b717..65ff74b4587 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxOnCloseTest.java @@ -50,11 +50,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class JavaxOnCloseTest { - private static BlockingArrayQueue serverEndpoints = new BlockingArrayQueue<>(); + private static final BlockingArrayQueue serverEndpoints = new BlockingArrayQueue<>(); private Server server; private ServerConnector connector; - private JavaxWebSocketClientContainer client = new JavaxWebSocketClientContainer(); + private final JavaxWebSocketClientContainer client = new JavaxWebSocketClientContainer(); @ServerEndpoint("/") public static class OnCloseEndpoint extends EventSocket @@ -84,7 +84,7 @@ public class JavaxOnCloseTest @ClientEndpoint public static class BlockingClientEndpoint extends EventSocket { - private CountDownLatch blockInClose = new CountDownLatch(1); + private final CountDownLatch blockInClose = new CountDownLatch(1); public void unBlockClose() { diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/EndpointEchoTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/EndpointEchoTest.java index 39066ed9167..e62d4c364e6 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/EndpointEchoTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/client/EndpointEchoTest.java @@ -18,13 +18,18 @@ package org.eclipse.jetty.websocket.javax.tests.client; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; +import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import javax.websocket.WebSocketContainer; +import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession; import org.eclipse.jetty.websocket.javax.tests.LocalServer; import org.eclipse.jetty.websocket.javax.tests.WSEndpointTracker; @@ -35,6 +40,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; public class EndpointEchoTest { @@ -105,4 +111,45 @@ public class EndpointEchoTest session.close(); endpoint.awaitCloseEvent("Client"); } + + @Test + public void testEchoAnonymousInstance() throws Exception + { + CountDownLatch openLatch = new CountDownLatch(1); + CountDownLatch closeLatch = new CountDownLatch(1); + BlockingQueue textMessages = new BlockingArrayQueue<>(); + Endpoint clientEndpoint = new Endpoint() + { + @Override + public void onOpen(Session session, EndpointConfig config) + { + // Cannot replace this with a lambda or it breaks ReflectUtils.findGenericClassFor(). + session.addMessageHandler(new MessageHandler.Whole() + { + @Override + public void onMessage(String message) + { + textMessages.add(message); + } + }); + openLatch.countDown(); + } + + @Override + public void onClose(Session session, CloseReason closeReason) + { + closeLatch.countDown(); + } + }; + + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + Session session = container.connectToServer(clientEndpoint, null, server.getWsUri().resolve("/echo/text")); + assertTrue(openLatch.await(5, TimeUnit.SECONDS)); + session.getBasicRemote().sendText("Echo"); + + String resp = textMessages.poll(1, TimeUnit.SECONDS); + assertThat("Response echo", resp, is("Echo")); + session.close(); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/EncoderLifeCycleTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/EncoderLifeCycleTest.java index cc03423accd..88657bbbd74 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/EncoderLifeCycleTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/EncoderLifeCycleTest.java @@ -40,7 +40,7 @@ import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; -import org.eclipse.jetty.websocket.javax.tests.EventSocket.EchoSocket; +import org.eclipse.jetty.websocket.javax.tests.EchoSocket; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java index 82f3fd3f76c..45cc8ca1778 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/ConfiguratorTest.java @@ -54,7 +54,7 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; -import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator; import org.eclipse.jetty.websocket.javax.tests.LocalServer; @@ -430,7 +430,7 @@ public class ConfiguratorTest URI wsUri = server.getWsUri().resolve("/capture-request-headers"); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.addExtensions("identity"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -454,7 +454,7 @@ public class ConfiguratorTest URI wsUri = server.getWsUri().resolve("/no-extensions"); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.addExtensions("identity"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -478,7 +478,7 @@ public class ConfiguratorTest URI wsUri = server.getWsUri().resolve("/capture-request-headers"); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.headers(headers -> headers.put("X-Dummy", "Bogus")); Future clientConnectFuture = client.connect(upgradeRequest); @@ -503,7 +503,7 @@ public class ConfiguratorTest // First Request FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); Future clientConnectFuture = client.connect(upgradeRequest); CoreSession coreSession = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS); @@ -522,7 +522,7 @@ public class ConfiguratorTest // Second request clientSocket = new FrameHandlerTracker(); - upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); clientConnectFuture = client.connect(upgradeRequest); coreSession = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS); @@ -550,7 +550,7 @@ public class ConfiguratorTest URI wsUri = server.getWsUri().resolve("/addr"); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); Future clientConnectFuture = client.connect(upgradeRequest); CoreSession coreSession = clientConnectFuture.get(Timeouts.CONNECT_MS, TimeUnit.MILLISECONDS); @@ -591,7 +591,7 @@ public class ConfiguratorTest ProtocolsConfigurator.seenProtocols.set(null); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.setSubProtocols("status"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -610,7 +610,7 @@ public class ConfiguratorTest ProtocolsConfigurator.seenProtocols.set(null); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.setSubProtocols("echo", "chat", "status"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -629,7 +629,7 @@ public class ConfiguratorTest ProtocolsConfigurator.seenProtocols.set(null); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.setSubProtocols("echo", "chat", "status"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -648,7 +648,7 @@ public class ConfiguratorTest ProtocolsConfigurator.seenProtocols.set(null); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.setSubProtocols("echo", "chat", "status"); Future clientConnectFuture = client.connect(upgradeRequest); @@ -681,7 +681,7 @@ public class ConfiguratorTest URI wsUri = server.getWsUri().resolve("/timedecoder"); FrameHandlerTracker clientSocket = new FrameHandlerTracker(); - ClientUpgradeRequest upgradeRequest = ClientUpgradeRequest.from(client, wsUri, clientSocket); + CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(client, wsUri, clientSocket); upgradeRequest.setSubProtocols("gmt"); Future clientConnectFuture = client.connect(upgradeRequest); diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionConfigurator.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionConfigurator.java similarity index 95% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionConfigurator.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionConfigurator.java index 80890503983..2f732934afe 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionConfigurator.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionConfigurator.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionSocket.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionSocket.java similarity index 96% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionSocket.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionSocket.java index 15daea9f5cd..56327b63174 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/GetHttpSessionSocket.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/GetHttpSessionSocket.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import java.io.IOException; import javax.servlet.http.HttpSession; diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedConfigurator.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedConfigurator.java similarity index 96% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedConfigurator.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedConfigurator.java index d633f2f26be..47b9f7e3d39 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedConfigurator.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedConfigurator.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import java.security.Principal; import javax.websocket.HandshakeResponse; diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedSocket.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedSocket.java similarity index 94% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedSocket.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedSocket.java index a2b6396d19e..cf3b8f10266 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/MyAuthedSocket.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/MyAuthedSocket.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/StreamingEchoSocket.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/StreamingEchoSocket.java similarity index 95% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/StreamingEchoSocket.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/StreamingEchoSocket.java index d5d86e451ec..4631a2a74cc 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/StreamingEchoSocket.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/StreamingEchoSocket.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import java.io.IOException; import java.io.Reader; diff --git a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/WebSocketServerExamplesTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/WebSocketServerExamplesTest.java similarity index 99% rename from jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/WebSocketServerExamplesTest.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/WebSocketServerExamplesTest.java index 22c96ca9772..0bdb21ba5e4 100644 --- a/jetty-websocket/websocket-javax-server/src/test/java/org/eclipse/jetty/websocket/javax/server/examples/WebSocketServerExamplesTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/examples/WebSocketServerExamplesTest.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.javax.server.examples; +package org.eclipse.jetty.websocket.javax.tests.server.examples; import java.net.URI; import java.util.concurrent.ArrayBlockingQueue; diff --git a/jetty-websocket/websocket-javax-server/src/test/resources/jetty-websocket-httpclient.xml b/jetty-websocket/websocket-javax-tests/src/test/resources/jetty-websocket-httpclient.xml similarity index 100% rename from jetty-websocket/websocket-javax-server/src/test/resources/jetty-websocket-httpclient.xml rename to jetty-websocket/websocket-javax-tests/src/test/resources/jetty-websocket-httpclient.xml diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java index 9db3f0a81f3..b96f9e67373 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java @@ -31,26 +31,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; */ public interface UpgradeRequest { - /** - * Add WebSocket Extension Configuration(s) to Upgrade Request. - *

- * This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was - * negotiated - * - * @param configs the configuration(s) to add - */ - void addExtensions(ExtensionConfig... configs); - - /** - * Add WebSocket Extension Configuration(s) to request - *

- * This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was - * negotiated - * - * @param configs the configuration(s) to add - */ - void addExtensions(String... configs); - /** * Get the list of Cookies on the Upgrade request * @@ -168,15 +148,6 @@ public interface UpgradeRequest */ URI getRequestURI(); - /** - * Access the Servlet HTTP Session (if present) - *

- * Note: Never present on a Client UpgradeRequest. - * - * @return the Servlet HTTPSession on server side UpgradeRequests - */ - Object getSession(); - /** * Get the list of offered WebSocket sub-protocols. * @@ -207,74 +178,4 @@ public interface UpgradeRequest * @return true if connection is secure. */ boolean isSecure(); - - /** - * Set the list of Cookies on the request - * - * @param cookies the cookies to use - */ - void setCookies(List cookies); - - /** - * Set the list of WebSocket Extension configurations on the request. - * - * @param configs the list of extension configurations - */ - void setExtensions(List configs); - - /** - * Set a specific header with multi-value field - *

- * Overrides any previous value for this named header - * - * @param name the name of the header - * @param values the multi-value field - */ - void setHeader(String name, List values); - - /** - * Set a specific header value - *

- * Overrides any previous value for this named header - * - * @param name the header to set - * @param value the value to set it to - */ - void setHeader(String name, String value); - - /** - * Sets multiple headers on the request. - *

- * Only sets those headers provided, does not remove - * headers that exist on request and are not provided in the - * parameter for this method. - *

- * Convenience method vs calling {@link #setHeader(String, List)} multiple times. - * - * @param headers the headers to set - */ - void setHeaders(Map> headers); - - /** - * Set the Session associated with this request. - *

- * Typically used to associate the Servlet HttpSession object. - * - * @param session the session object to associate with this request - */ - void setSession(Object session); - - /** - * Set the offered WebSocket Sub-Protocol list. - * - * @param protocols the offered sub-protocol list - */ - void setSubProtocols(List protocols); - - /** - * Set the offered WebSocket Sub-Protocol list. - * - * @param protocols the offered sub-protocol list - */ - void setSubProtocols(String... protocols); } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java index 922f3764edc..6a8ab84dc14 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.websocket.api; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,14 +29,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; */ public interface UpgradeResponse { - /** - * Add a header value to the response. - * - * @param name the header name - * @param value the header value - */ - void addHeader(String name, String value); - /** * Get the accepted WebSocket protocol. * @@ -88,57 +79,4 @@ public interface UpgradeResponse * @return the status code */ int getStatusCode(); - - /** - * Issue a forbidden upgrade response. - *

- * This means that the websocket endpoint was valid, but the conditions to use a WebSocket resulted in a forbidden - * access. - *

- * Use this when the origin or authentication is invalid. - * - * @param message the short 1 line detail message about the forbidden response - * @throws IOException if unable to send the forbidden - */ - void sendForbidden(String message) throws IOException; - - /** - * Set the accepted WebSocket Protocol. - * - * @param protocol the protocol to list as accepted - */ - void setAcceptedSubProtocol(String protocol); - - /** - * Set the list of extensions that are approved for use with this websocket. - *

- * Notes: - *

    - *
  • Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove - * entries you don't want to use
  • - *
  • If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the - * complete list of extensions that are - * available in this WebSocket server implementation.
  • - *
- * - * @param extensions the list of extensions to use. - */ - void setExtensions(List extensions); - - /** - * Set a header - *

- * Overrides previous value of header (if set) - * - * @param name the header name - * @param value the header value - */ - void setHeader(String name, String value); - - /** - * Set the HTTP Response status code - * - * @param statusCode the status code - */ - void setStatusCode(int statusCode); } diff --git a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketSessionListener.java b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketSessionListener.java index 22d13152b77..8a809f99708 100644 --- a/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketSessionListener.java +++ b/jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketSessionListener.java @@ -23,6 +23,10 @@ package org.eclipse.jetty.websocket.api; */ public interface WebSocketSessionListener { + default void onWebSocketSessionCreated(Session session) + { + } + default void onWebSocketSessionOpened(Session session) { } diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java index fb41b6af2cd..06dc403d96d 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java @@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; /** @@ -63,53 +64,18 @@ public final class ClientUpgradeRequest implements UpgradeRequest this.host = this.requestURI.getHost(); } - @Override - public void addExtensions(ExtensionConfig... configs) - { - Collections.addAll(extensions, configs); - } - - @Override - public void addExtensions(String... configs) - { - for (String config : configs) - { - extensions.add(ExtensionConfig.parse(config)); - } - } - @Override public List getCookies() { return cookies; } - @Override - public void setCookies(List cookies) - { - this.cookies.clear(); - if (cookies != null && !cookies.isEmpty()) - { - this.cookies.addAll(cookies); - } - } - @Override public List getExtensions() { return extensions; } - @Override - public void setExtensions(List configs) - { - this.extensions.clear(); - if (configs != null) - { - this.extensions.addAll(configs); - } - } - @Override public String getHeader(String name) { @@ -201,12 +167,6 @@ public final class ClientUpgradeRequest implements UpgradeRequest return requestURI; } - @Override - public Object getSession() - { - throw new UnsupportedOperationException("HttpSession not available on Client request"); - } - @Override public List getSubProtocols() { @@ -219,28 +179,6 @@ public final class ClientUpgradeRequest implements UpgradeRequest throw new UnsupportedOperationException("User Principal not available on Client request"); } - /** - * Set Sub Protocol request list. - * - * @param protocols the sub protocols desired - */ - @Override - public void setSubProtocols(String... protocols) - { - subProtocols.clear(); - Collections.addAll(subProtocols, protocols); - } - - @Override - public void setSubProtocols(List subProtocols) - { - this.subProtocols.clear(); - if (subProtocols != null) - { - this.subProtocols.addAll(subProtocols); - } - } - @Override public boolean hasSubProtocol(String test) { @@ -260,13 +198,84 @@ public final class ClientUpgradeRequest implements UpgradeRequest throw new UnsupportedOperationException("Request.isSecure not available on Client request"); } - @Override + /** + * Add WebSocket Extension Configuration(s) to Upgrade Request. + *

+ * This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was + * negotiated + * + * @param configs the configuration(s) to add + */ + public void addExtensions(ExtensionConfig... configs) + { + Collections.addAll(extensions, configs); + } + + /** + * Add WebSocket Extension Configuration(s) to request + *

+ * This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was + * negotiated + * + * @param configs the configuration(s) to add + */ + public void addExtensions(String... configs) + { + for (String config : configs) + { + extensions.add(ExtensionConfig.parse(config)); + } + } + + /** + * Set the list of Cookies on the request + * + * @param cookies the cookies to use + */ + public void setCookies(List cookies) + { + this.cookies.clear(); + if (cookies != null && !cookies.isEmpty()) + { + this.cookies.addAll(cookies); + } + } + + /** + * Set the list of WebSocket Extension configurations on the request. + * + * @param configs the list of extension configurations + */ + public void setExtensions(List configs) + { + this.extensions.clear(); + if (configs != null) + { + this.extensions.addAll(configs); + } + } + + /** + * Set a specific header with multi-value field + *

+ * Overrides any previous value for this named header + * + * @param name the name of the header + * @param values the multi-value field + */ public void setHeader(String name, List values) { headers.put(name, values); } - @Override + /** + * Set a specific header value + *

+ * Overrides any previous value for this named header + * + * @param name the header to set + * @param value the value to set it to + */ public void setHeader(String name, String value) { List values = new ArrayList<>(); @@ -274,7 +283,17 @@ public final class ClientUpgradeRequest implements UpgradeRequest setHeader(name, values); } - @Override + /** + * Sets multiple headers on the request. + *

+ * Only sets those headers provided, does not remove + * headers that exist on request and are not provided in the + * parameter for this method. + *

+ * Convenience method vs calling {@link #setHeader(String, List)} multiple times. + * + * @param headers the headers to set + */ public void setHeaders(Map> headers) { this.headers.clear(); @@ -286,10 +305,29 @@ public final class ClientUpgradeRequest implements UpgradeRequest } } - @Override - public void setSession(Object session) + /** + * Set the offered WebSocket Sub-Protocol list. + * + * @param protocols the offered sub-protocol list + */ + public void setSubProtocols(List protocols) { - throw new UnsupportedOperationException("HttpSession not available on Client request"); + this.subProtocols.clear(); + if (protocols != null) + { + this.subProtocols.addAll(protocols); + } + } + + /** + * Set the offered WebSocket Sub-Protocol list. + * + * @param protocols the offered sub-protocol list + */ + public void setSubProtocols(String... protocols) + { + subProtocols.clear(); + Collections.addAll(subProtocols, protocols); } /** diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index ed36d8a0906..9abdbbed8af 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -29,6 +29,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.eclipse.jetty.client.HttpClient; @@ -38,6 +39,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.websocket.api.Session; @@ -50,6 +52,7 @@ import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.common.SessionTracker; import org.eclipse.jetty.websocket.core.Configuration; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.client.UpgradeListener; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; @@ -67,6 +70,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli private final Configuration.ConfigurationCustomizer configurationCustomizer = new Configuration.ConfigurationCustomizer(); private final WebSocketComponents components = new WebSocketComponents(); private boolean stopAtShutdown = false; + private long _stopTimeout = Long.MAX_VALUE; /** * Instantiate a WebSocketClient with defaults @@ -151,7 +155,8 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli } CompletableFuture futureSession = new CompletableFuture<>(); - coreClient.connect(upgradeRequest).whenComplete((coreSession, error) -> + CompletableFuture coreConnect = coreClient.connect(upgradeRequest); + coreConnect.whenComplete((coreSession, error) -> { if (error != null) { @@ -163,6 +168,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli futureSession.complete(frameHandler.getSession()); }); + // If the returned future is cancelled we want to try to cancel the core future if possible. + futureSession.whenComplete((session, throwable) -> + { + if (throwable != null) + coreConnect.completeExceptionally(throwable); + }); return futureSession; } @@ -367,7 +378,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli * @see Runtime#addShutdownHook(Thread) * @see ShutdownThread */ - public synchronized void setStopAtShutdown(boolean stop) + public void setStopAtShutdown(boolean stop) { if (stop) { @@ -380,11 +391,33 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli stopAtShutdown = stop; } + /** + * The timeout to allow all remaining open Sessions to be closed gracefully using the close code {@link org.eclipse.jetty.websocket.api.StatusCode#SHUTDOWN}. + * @param stopTimeout the time in ms to wait for the graceful close, use a value less than or equal to 0 to not gracefully close. + */ + public void setStopTimeout(long stopTimeout) + { + _stopTimeout = stopTimeout; + } + + public long getStopTimeout() + { + return _stopTimeout; + } + public boolean isStopAtShutdown() { return stopAtShutdown; } + @Override + protected void doStop() throws Exception + { + if (getStopTimeout() > 0) + Graceful.shutdown(this).get(getStopTimeout(), TimeUnit.MILLISECONDS); + super.doStop(); + } + @Override public String toString() { diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java index bdd1a38227d..06a18e11d61 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.websocket.client.impl; import java.net.HttpCookie; -import java.net.SocketAddress; import java.net.URI; import java.security.Principal; import java.util.Collections; @@ -27,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.io.EndPoint; @@ -34,7 +34,7 @@ import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import static java.nio.charset.StandardCharsets.UTF_8; @@ -44,11 +44,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; */ public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest { - private final ClientUpgradeRequest delegate; - private SocketAddress localSocketAddress; - private SocketAddress remoteSocketAddress; + private final CoreClientUpgradeRequest delegate; - public DelegatedJettyClientUpgradeRequest(ClientUpgradeRequest delegate) + public DelegatedJettyClientUpgradeRequest(CoreClientUpgradeRequest delegate) { this.delegate = delegate; } @@ -100,8 +98,6 @@ public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest public void configure(EndPoint endpoint) { - this.localSocketAddress = endpoint.getLocalAddress(); - this.remoteSocketAddress = endpoint.getRemoteAddress(); } @Override @@ -163,26 +159,7 @@ public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest @Override public boolean isSecure() { - // TODO: figure out how to obtain from HttpClient's HttpRequest - return false; - } - - @Override - public void addExtensions(org.eclipse.jetty.websocket.api.extensions.ExtensionConfig... configs) - { - // TODO - } - - @Override - public void addExtensions(String... configs) - { - // TODO - } - - @Override - public Object getSession() - { - return null; + return HttpClient.isSchemeSecure(delegate.getScheme()); } @Override @@ -191,12 +168,6 @@ public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest return null; } - @Override - public void setCookies(List cookies) - { - // TODO - } - @Override public List getExtensions() { @@ -204,48 +175,6 @@ public class DelegatedJettyClientUpgradeRequest implements UpgradeRequest if (rawExtensions == null || rawExtensions.isEmpty()) return Collections.emptyList(); - return rawExtensions.stream().map((parameterizedName) -> ExtensionConfig.parse(parameterizedName)).collect(Collectors.toList()); - } - - @Override - public void setExtensions(List configs) - { - // TODO - } - - @Override - public void setHeader(String name, List values) - { - // TODO - } - - @Override - public void setHeader(String name, String value) - { - // TODO - } - - @Override - public void setHeaders(Map> headers) - { - // TODO - } - - @Override - public void setSession(Object session) - { - // TODO - } - - @Override - public void setSubProtocols(List protocols) - { - // TODO - } - - @Override - public void setSubProtocols(String... protocols) - { - // TODO + return rawExtensions.stream().map(ExtensionConfig::parse).collect(Collectors.toList()); } } diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java index 67cad5ee72d..79eb3f5d54c 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.websocket.client.impl; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -36,7 +35,7 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; */ public class DelegatedJettyClientUpgradeResponse implements UpgradeResponse { - private HttpResponse delegate; + private final HttpResponse delegate; public DelegatedJettyClientUpgradeResponse(HttpResponse response) { @@ -79,24 +78,6 @@ public class DelegatedJettyClientUpgradeResponse implements UpgradeResponse return this.delegate.getStatus(); } - @Override - public void addHeader(String name, String value) - { - - } - - @Override - public void sendForbidden(String message) throws IOException - { - - } - - @Override - public void setAcceptedSubProtocol(String protocol) - { - - } - @Override public List getExtensions() { @@ -104,24 +85,6 @@ public class DelegatedJettyClientUpgradeResponse implements UpgradeResponse if (rawExtensions == null || rawExtensions.isEmpty()) return Collections.emptyList(); - return rawExtensions.stream().map((parameterizedName) -> ExtensionConfig.parse(parameterizedName)).collect(Collectors.toList()); - } - - @Override - public void setExtensions(List extensions) - { - - } - - @Override - public void setHeader(String name, String value) - { - - } - - @Override - public void setStatusCode(int statusCode) - { - + return rawExtensions.stream().map(ExtensionConfig::parse).collect(Collectors.toList()); } } diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java index f91f1221434..36f971f9c42 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/impl/JettyClientUpgradeRequest.java @@ -30,11 +30,11 @@ import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.FrameHandler; +import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; -public class JettyClientUpgradeRequest extends org.eclipse.jetty.websocket.core.client.ClientUpgradeRequest +public class JettyClientUpgradeRequest extends CoreClientUpgradeRequest { - private final DelegatedJettyClientUpgradeRequest handshakeRequest; private final JettyWebSocketFrameHandler frameHandler; public JettyClientUpgradeRequest(WebSocketCoreClient coreClient, ClientUpgradeRequest request, URI requestURI, JettyWebSocketFrameHandlerFactory frameHandlerFactory, @@ -58,17 +58,9 @@ public class JettyClientUpgradeRequest extends org.eclipse.jetty.websocket.core. timeout(request.getTimeout(), TimeUnit.MILLISECONDS); } - handshakeRequest = new DelegatedJettyClientUpgradeRequest(this); frameHandler = frameHandlerFactory.newJettyFrameHandler(websocketPojo); } - @Override - protected void customize(EndPoint endPoint) - { - super.customize(endPoint); - handshakeRequest.configure(endPoint); - } - @Override public void upgrade(HttpResponse response, EndPoint endPoint) { diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index dee0c4f033c..fefc6a32c1d 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; @@ -60,6 +61,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler CLOSED } + private final AutoLock lock = new AutoLock(); private final Logger log; private final WebSocketContainer container; private final Object endpointInstance; @@ -153,7 +155,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler try { customizer.customize(coreSession); - session = new WebSocketSession(coreSession, this); + session = new WebSocketSession(container, coreSession, this); frameHandle = InvokerUtils.bindTo(frameHandle, session); openHandle = InvokerUtils.bindTo(openHandle, session); @@ -189,7 +191,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler @Override public void onFrame(Frame frame, Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { switch (state) { @@ -298,7 +300,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler @Override public void onClosed(CloseStatus closeStatus, Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { // We are now closed and cannot suspend or resume. state = SuspendState.CLOSED; @@ -425,7 +427,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler public void suspend() { - synchronized (this) + try (AutoLock l = lock.lock()) { switch (state) { @@ -443,7 +445,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler { boolean needDemand = false; Runnable delayedFrame = null; - synchronized (this) + try (AutoLock l = lock.lock()) { switch (state) { @@ -480,7 +482,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler private void demand() { boolean demand = false; - synchronized (this) + try (AutoLock l = lock.lock()) { switch (state) { diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index 0d08b373c4f..da579a24477 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -195,28 +195,31 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); - MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getApplicationMethodHandleLookup(endpointClass); + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); - Method openMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketConnect", Session.class); + if (!WebSocketConnectionListener.class.isAssignableFrom(endpointClass)) + throw new IllegalArgumentException("Class " + endpointClass + " does not implement " + WebSocketConnectionListener.class); + + Method openMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketConnect", Session.class); MethodHandle open = toMethodHandle(lookup, openMethod); metadata.setOpenHandler(open, openMethod); - Method closeMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketClose", int.class, String.class); + Method closeMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketClose", int.class, String.class); MethodHandle close = toMethodHandle(lookup, closeMethod); metadata.setCloseHandler(close, closeMethod); - Method errorMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketError", Throwable.class); + Method errorMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketError", Throwable.class); MethodHandle error = toMethodHandle(lookup, errorMethod); metadata.setErrorHandler(error, errorMethod); // Simple Data Listener if (WebSocketListener.class.isAssignableFrom(endpointClass)) { - Method textMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketText", String.class); + Method textMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketText", String.class); MethodHandle text = toMethodHandle(lookup, textMethod); metadata.setTextHandler(StringMessageSink.class, text, textMethod); - Method binaryMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketBinary", byte[].class, int.class, int.class); + Method binaryMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketBinary", byte[].class, int.class, int.class); MethodHandle binary = toMethodHandle(lookup, binaryMethod); metadata.setBinaryHandle(ByteArrayMessageSink.class, binary, binaryMethod); } @@ -224,11 +227,11 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // Ping/Pong Listener if (WebSocketPingPongListener.class.isAssignableFrom(endpointClass)) { - Method pongMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPong", ByteBuffer.class); + Method pongMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPong", ByteBuffer.class); MethodHandle pong = toMethodHandle(lookup, pongMethod); metadata.setPongHandle(pong, pongMethod); - Method pingMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPing", ByteBuffer.class); + Method pingMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPing", ByteBuffer.class); MethodHandle ping = toMethodHandle(lookup, pingMethod); metadata.setPingHandle(ping, pingMethod); } @@ -236,11 +239,11 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // Partial Data / Message Listener if (WebSocketPartialListener.class.isAssignableFrom(endpointClass)) { - Method textMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPartialText", String.class, boolean.class); + Method textMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialText", String.class, boolean.class); MethodHandle text = toMethodHandle(lookup, textMethod); metadata.setTextHandler(PartialStringMessageSink.class, text, textMethod); - Method binaryMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class); + Method binaryMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class); MethodHandle binary = toMethodHandle(lookup, binaryMethod); metadata.setBinaryHandle(PartialByteBufferMessageSink.class, binary, binaryMethod); } @@ -248,7 +251,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle // Frame Listener if (WebSocketFrameListener.class.isAssignableFrom(endpointClass)) { - Method frameMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketFrame", Frame.class); + Method frameMethod = ReflectUtils.findMethod(WebSocketFrameListener.class, "onWebSocketFrame", Frame.class); MethodHandle frame = toMethodHandle(lookup, frameMethod); metadata.setFrameHandler(frame, frameMethod); } diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java index e963800ae9f..baa5bb46a35 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java @@ -18,22 +18,29 @@ package org.eclipse.jetty.websocket.common; +import java.io.IOException; import java.util.Collection; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; -public class SessionTracker extends AbstractLifeCycle implements WebSocketSessionListener +public class SessionTracker extends AbstractLifeCycle implements WebSocketSessionListener, Graceful, Dumpable { - private List sessions = new CopyOnWriteArrayList<>(); + private final Set sessions = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private boolean isShutdown = false; public Collection getSessions() { - return sessions; + return Set.copyOf(sessions); } @Override @@ -48,15 +55,52 @@ public class SessionTracker extends AbstractLifeCycle implements WebSocketSessio sessions.remove(session); } + @Override + protected void doStart() throws Exception + { + isShutdown = false; + super.doStart(); + } + @Override protected void doStop() throws Exception { - for (Session session : sessions) - { - // SHUTDOWN is abnormal close status so it will hard close connection after sent. - session.close(StatusCode.SHUTDOWN, "Container being shut down"); - } - + sessions.clear(); super.doStop(); } + + @Override + public CompletableFuture shutdown() + { + isShutdown = true; + return Graceful.shutdown(() -> + { + for (Session session : sessions) + { + if (Thread.interrupted()) + break; + + // SHUTDOWN is abnormal close status so it will hard close connection after sent. + session.close(StatusCode.SHUTDOWN, "Container being shut down"); + } + }); + } + + @Override + public boolean isShutdown() + { + return isShutdown; + } + + @ManagedAttribute("Total number of active WebSocket Sessions") + public int getNumSessions() + { + return sessions.size(); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + Dumpable.dumpObjects(out, indent, this, sessions); + } } diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index 6f1c2a5988a..47b234025a2 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -26,10 +26,12 @@ import java.util.Objects; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.websocket.api.CloseStatus; import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketContainer; import org.eclipse.jetty.websocket.core.CoreSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,19 +45,20 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable private final UpgradeRequest upgradeRequest; private final UpgradeResponse upgradeResponse; - public WebSocketSession(CoreSession coreSession, JettyWebSocketFrameHandler frameHandler) + public WebSocketSession(WebSocketContainer container, CoreSession coreSession, JettyWebSocketFrameHandler frameHandler) { this.frameHandler = Objects.requireNonNull(frameHandler); this.coreSession = Objects.requireNonNull(coreSession); this.upgradeRequest = frameHandler.getUpgradeRequest(); this.upgradeResponse = frameHandler.getUpgradeResponse(); this.remoteEndpoint = new JettyWebSocketRemoteEndpoint(coreSession, frameHandler.getBatchMode()); + container.notifySessionListeners((listener) -> listener.onWebSocketSessionCreated(this)); } @Override public void close() { - remoteEndpoint.close(); + remoteEndpoint.close(StatusCode.NORMAL, null); } @Override diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java index 13e0b330d61..53c2bac0a2e 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java @@ -18,310 +18,99 @@ package org.eclipse.jetty.websocket.server; -import java.net.HttpCookie; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.net.URI; -import java.security.Principal; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.common.JettyExtensionConfig; -import org.eclipse.jetty.websocket.core.server.Negotiation; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.api.UpgradeRequest; -public class JettyServerUpgradeRequest +public interface JettyServerUpgradeRequest extends UpgradeRequest { - private ServletUpgradeRequest upgradeRequest; - - JettyServerUpgradeRequest(ServletUpgradeRequest request) - { - upgradeRequest = request; - } + /** + * Access the Servlet HTTP Session (if present) + *

+ * Note: Never present on a Client UpgradeRequest. + * + * @return the Servlet HTTPSession on server side UpgradeRequests + */ + Object getSession(); /** * @return The {@link X509Certificate} instance at request attribute "javax.servlet.request.X509Certificate" or null. */ - public X509Certificate[] getCertificates() - { - return upgradeRequest.getCertificates(); - } - - /** - * @return Request cookies - * @see HttpServletRequest#getCookies() - */ - public List getCookies() - { - return upgradeRequest.getCookies(); - } - - /** - * @return The extensions offered - * @see Negotiation#getOfferedExtensions() - */ - public List getExtensions() - { - return upgradeRequest.getExtensions().stream().map(JettyExtensionConfig::new).collect(Collectors.toList()); - } - - /** - * @param name Header name - * @return Header value or null - * @see HttpServletRequest#getHeader(String) - */ - public String getHeader(String name) - { - return upgradeRequest.getHeader(name); - } - - /** - * @param name Header name - * @return Header value as integer or -1 - * @see HttpServletRequest#getHeader(String) - */ - public int getHeaderInt(String name) - { - return upgradeRequest.getHeaderInt(name); - } - - /** - * @return Map of headers - */ - public Map> getHeadersMap() - { - return upgradeRequest.getHeadersMap(); - } - - /** - * @param name Header name - * @return List of header values or null - */ - public List getHeaders(String name) - { - return upgradeRequest.getHeaders(name); - } - - /** - * @return The requested host - * @see HttpServletRequest#getRequestURL() - */ - public String getHost() - { - return upgradeRequest.getHost(); - } + X509Certificate[] getCertificates(); /** * @return Immutable version of {@link HttpServletRequest} */ - public HttpServletRequest getHttpServletRequest() - { - return upgradeRequest.getHttpServletRequest(); - } - - /** - * @return The HTTP protocol version - * @see HttpServletRequest#getProtocol() - */ - public String getHttpVersion() - { - return upgradeRequest.getHttpVersion(); - } + HttpServletRequest getHttpServletRequest(); /** * @return The requested Locale * @see HttpServletRequest#getLocale() */ - public Locale getLocale() - { - return upgradeRequest.getLocale(); - } + Locale getLocale(); /** * @return The requested Locales * @see HttpServletRequest#getLocales() */ - public Enumeration getLocales() - { - return upgradeRequest.getLocales(); - } + Enumeration getLocales(); /** * @return The local requested address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress} * @see ServletRequest#getLocalAddr() * @see ServletRequest#getLocalPort() */ - public SocketAddress getLocalSocketAddress() - { - return upgradeRequest.getLocalSocketAddress(); - } - - /** - * @return The requested method - * @see HttpServletRequest#getMethod() - */ - public String getMethod() - { - return upgradeRequest.getMethod(); - } - - /** - * @return The origin header value - */ - public String getOrigin() - { - return upgradeRequest.getOrigin(); - } - - /** - * @return The request parameter map - * @see ServletRequest#getParameterMap() - */ - public Map> getParameterMap() - { - return upgradeRequest.getParameterMap(); - } - - /** - * @return WebSocket protocol version from "Sec-WebSocket-Version" header - */ - public String getProtocolVersion() - { - return upgradeRequest.getProtocolVersion(); - } - - /** - * @return The request query string - * @see HttpServletRequest#getQueryString() - */ - public String getQueryString() - { - return upgradeRequest.getQueryString(); - } + SocketAddress getLocalSocketAddress(); /** * @return The remote request address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress} * @see ServletRequest#getRemoteAddr() * @see ServletRequest#getRemotePort() */ - public SocketAddress getRemoteSocketAddress() - { - return upgradeRequest.getRemoteSocketAddress(); - } + SocketAddress getRemoteSocketAddress(); /** * @return The request URI path within the context */ - public String getRequestPath() - { - return upgradeRequest.getRequestPath(); - } - - /** - * @return The request URI - * @see HttpServletRequest#getRequestURL() - */ - public URI getRequestURI() - { - return upgradeRequest.getRequestURI(); - } + String getRequestPath(); /** * @param name Attribute name * @return Attribute value or null * @see ServletRequest#getAttribute(String) */ - public Object getServletAttribute(String name) - { - return upgradeRequest.getServletAttribute(name); - } + Object getServletAttribute(String name); /** * @return Request attribute map */ - public Map getServletAttributes() - { - return upgradeRequest.getServletAttributes(); - } + Map getServletAttributes(); /** * @return Request parameters * @see ServletRequest#getParameterMap() */ - public Map> getServletParameters() - { - return upgradeRequest.getServletParameters(); - } - - /** - * @return The HttpSession, which may be null or invalidated - * @see HttpServletRequest#getSession(boolean) - */ - public HttpSession getSession() - { - return upgradeRequest.getSession(); - } - - /** - * @return Get WebSocket negotiation offered sub protocols - */ - public List getSubProtocols() - { - return upgradeRequest.getSubProtocols(); - } - - /** - * @return The User's {@link Principal} or null - * @see HttpServletRequest#getUserPrincipal() - */ - public Principal getUserPrincipal() - { - return upgradeRequest.getUserPrincipal(); - } - - /** - * @param subprotocol A sub protocol name - * @return True if the sub protocol was offered - */ - public boolean hasSubProtocol(String subprotocol) - { - return upgradeRequest.hasSubProtocol(subprotocol); - } - - /** - * @return True if the request is secure - * @see ServletRequest#isSecure() - */ - public boolean isSecure() - { - return upgradeRequest.isSecure(); - } + Map> getServletParameters(); /** * @param role The user role * @return True if the requests user has the role * @see HttpServletRequest#isUserInRole(String) */ - public boolean isUserInRole(String role) - { - return upgradeRequest.isUserInRole(role); - } + boolean isUserInRole(String role); /** * @param name Attribute name * @param value Attribute value to set * @see ServletRequest#setAttribute(String, Object) */ - public void setServletAttribute(String name, Object value) - { - upgradeRequest.setServletAttribute(name, value); - } + void setServletAttribute(String name, Object value); } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java index 01e015302ed..bd6fe1c9e7e 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java @@ -20,102 +20,97 @@ package org.eclipse.jetty.websocket.server; import java.io.IOException; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.common.JettyExtensionConfig; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; -public class JettyServerUpgradeResponse +public interface JettyServerUpgradeResponse extends UpgradeResponse { - private ServletUpgradeResponse upgradeResponse; + /** + * Add a header value to the response. + * + * @param name the header name + * @param value the header value + */ + void addHeader(String name, String value); - JettyServerUpgradeResponse(ServletUpgradeResponse response) - { - upgradeResponse = response; - } + /** + * Set a header + *

+ * Overrides previous value of header (if set) + * + * @param name the header name + * @param value the header value + */ + void setHeader(String name, String value); - public void addHeader(String name, String value) - { - upgradeResponse.addHeader(name, value); - } + /** + * Set a header + *

+ * Overrides previous value of header (if set) + * + * @param name the header name + * @param values the header values + */ + void setHeader(String name, List values); - public void setHeader(String name, String value) - { - upgradeResponse.setHeader(name, value); - } + /** + * Issue a forbidden upgrade response. + *

+ * This means that the websocket endpoint was valid, but the conditions to use a WebSocket resulted in a forbidden + * access. + *

+ * Use this when the origin or authentication is invalid. + * + * @param message the short 1 line detail message about the forbidden response + * @throws IOException if unable to send the forbidden + */ + void sendForbidden(String message) throws IOException; - public void setHeader(String name, List values) - { - upgradeResponse.setHeader(name, values); - } + /** + * Sends an error response to the client using the specified status. + * @param statusCode the error status code + * @param message the descriptive message + * @throws IOException If an input or output exception occurs + * @throws IllegalStateException If the response was committed + */ + void sendError(int statusCode, String message) throws IOException; - public String getAcceptedSubProtocol() - { - return upgradeResponse.getAcceptedSubProtocol(); - } + /** + * Set the accepted WebSocket Protocol. + * + * @param protocol the protocol to list as accepted + */ + void setAcceptedSubProtocol(String protocol); - public List getExtensions() - { - return upgradeResponse.getExtensions().stream().map(JettyExtensionConfig::new).collect(Collectors.toList()); - } + /** + * Set the list of extensions that are approved for use with this websocket. + *

+ * Notes: + *

    + *
  • Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove + * entries you don't want to use
  • + *
  • If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the + * complete list of extensions that are + * available in this WebSocket server implementation.
  • + *
+ * + * @param extensions the list of extensions to use. + */ + void setExtensions(List extensions); - public String getHeader(String name) - { - return upgradeResponse.getHeader(name); - } + /** + * Set the HTTP Response status code + * + * @param statusCode the status code + */ + void setStatusCode(int statusCode); - public Set getHeaderNames() - { - return upgradeResponse.getHeaderNames(); - } - - public Map> getHeadersMap() - { - return upgradeResponse.getHeadersMap(); - } - - public List getHeaders(String name) - { - return upgradeResponse.getHeaders(name); - } - - public int getStatusCode() - { - return upgradeResponse.getStatusCode(); - } - - public boolean isCommitted() - { - return upgradeResponse.isCommitted(); - } - - public void sendError(int statusCode, String message) throws IOException - { - upgradeResponse.sendError(statusCode, message); - } - - public void sendForbidden(String message) throws IOException - { - upgradeResponse.sendForbidden(message); - } - - public void setAcceptedSubProtocol(String protocol) - { - upgradeResponse.setAcceptedSubProtocol(protocol); - } - - public void setExtensions(List configs) - { - upgradeResponse.setExtensions(configs.stream() - .map(c -> new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters())) - .collect(Collectors.toList())); - } - - public void setStatusCode(int statusCode) - { - upgradeResponse.setStatusCode(statusCode); - } + /** + * Returns a boolean indicating if the response has been committed. + * A committed response has already had its status code and headers written. + * @return a boolean indicating if the response has been committed. + */ + boolean isCommitted(); } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index bc6c85030e8..9609ba815cc 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -41,7 +41,11 @@ import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.WebSocketException; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeRequest; +import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeResponse; import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory; +import org.eclipse.jetty.websocket.util.ReflectUtils; +import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; import org.slf4j.Logger; @@ -85,8 +89,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements private static final Logger LOG = LoggerFactory.getLogger(JettyWebSocketServerContainer.class); + private final ServletContextHandler contextHandler; private final WebSocketMapping webSocketMapping; - private final WebSocketComponents webSocketComponents; private final FrameHandlerFactory frameHandlerFactory; private final Executor executor; private final Configuration.ConfigurationCustomizer customizer = new Configuration.ConfigurationCustomizer(); @@ -103,8 +107,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements */ JettyWebSocketServerContainer(ServletContextHandler contextHandler, WebSocketMapping webSocketMapping, WebSocketComponents webSocketComponents, Executor executor) { + this.contextHandler = contextHandler; this.webSocketMapping = webSocketMapping; - this.webSocketComponents = webSocketComponents; this.executor = executor; // Ensure there is a FrameHandlerFactory @@ -118,6 +122,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements frameHandlerFactory = factory; addSessionListener(sessionTracker); + addBean(sessionTracker); } public void addMapping(String pathSpec, JettyWebSocketCreator creator) @@ -126,11 +131,30 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements if (webSocketMapping.getMapping(ps) != null) throw new WebSocketException("Duplicate WebSocket Mapping for PathSpec"); + WebSocketUpgradeFilter.ensureFilter(contextHandler.getServletContext()); webSocketMapping.addMapping(ps, - (req, resp) -> creator.createWebSocket(new JettyServerUpgradeRequest(req), new JettyServerUpgradeResponse(resp)), + (req, resp) -> creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)), frameHandlerFactory, customizer); } + public void addMapping(String pathSpec, final Class endpointClass) + { + if (!ReflectUtils.isDefaultConstructable(endpointClass)) + throw new IllegalArgumentException("Cannot access default constructor for the class: " + endpointClass.getName()); + + addMapping(pathSpec, (req, resp) -> + { + try + { + return endpointClass.getDeclaredConstructor().newInstance(); + } + catch (Exception e) + { + throw new org.eclipse.jetty.websocket.api.WebSocketException("Unable to create instance of " + endpointClass.getName(), e); + } + }); + } + @Override public Executor getExecutor() { diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java index be184470323..0cbdd6b1212 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServlet.java @@ -32,11 +32,13 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; +import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeRequest; +import org.eclipse.jetty.websocket.server.internal.DelegatedServerUpgradeResponse; import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; import org.eclipse.jetty.websocket.util.server.internal.WebSocketCreator; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; import org.slf4j.Logger; @@ -276,9 +278,9 @@ public abstract class JettyWebSocketServlet extends HttpServlet } @Override - public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp) { - return creator.createWebSocket(new JettyServerUpgradeRequest(req), new JettyServerUpgradeResponse(resp)); + return creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); } } } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index 3b164c017f8..4740fbc75f8 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -22,13 +22,11 @@ import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; -import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; -import org.eclipse.jetty.websocket.util.server.WebSocketUpgradeFilter; import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,12 +89,11 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain private static JettyWebSocketServerContainer initialize(ServletContextHandler context) { WebSocketComponents components = WebSocketServerComponents.ensureWebSocketComponents(context.getServletContext()); - FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); JettyWebSocketServerContainer container = JettyWebSocketServerContainer.ensureContainer(context.getServletContext()); if (LOG.isDebugEnabled()) - LOG.debug("configureContext {} {} {} {}", container, mapping, filterHolder, components); + LOG.debug("configureContext {} {} {}", container, mapping, components); return container; } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeRequest.java new file mode 100644 index 00000000000..2973aa53eba --- /dev/null +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeRequest.java @@ -0,0 +1,235 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.server.internal; + +import java.net.HttpCookie; +import java.net.SocketAddress; +import java.net.URI; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.common.JettyExtensionConfig; +import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; + +public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest +{ + private final ServerUpgradeRequest upgradeRequest; + + public DelegatedServerUpgradeRequest(ServerUpgradeRequest request) + { + upgradeRequest = request; + } + + @Override + public List getCookies() + { + return upgradeRequest.getCookies(); + } + + @Override + public List getExtensions() + { + return upgradeRequest.getExtensions().stream() + .map(JettyExtensionConfig::new) + .collect(Collectors.toList()); + } + + @Override + public String getHeader(String name) + { + return upgradeRequest.getHeader(name); + } + + @Override + public int getHeaderInt(String name) + { + return upgradeRequest.getHeaderInt(name); + } + + @Override + public Map> getHeaders() + { + return upgradeRequest.getHeadersMap(); + } + + @Override + public List getHeaders(String name) + { + return upgradeRequest.getHeaders(name); + } + + @Override + public String getHost() + { + return upgradeRequest.getHost(); + } + + @Override + public String getHttpVersion() + { + return upgradeRequest.getHttpVersion(); + } + + @Override + public String getMethod() + { + return upgradeRequest.getMethod(); + } + + @Override + public String getOrigin() + { + return upgradeRequest.getOrigin(); + } + + @Override + public Map> getParameterMap() + { + return upgradeRequest.getParameterMap(); + } + + @Override + public String getProtocolVersion() + { + return upgradeRequest.getProtocolVersion(); + } + + @Override + public String getQueryString() + { + return upgradeRequest.getQueryString(); + } + + @Override + public URI getRequestURI() + { + return upgradeRequest.getRequestURI(); + } + + @Override + public HttpSession getSession() + { + return upgradeRequest.getSession(); + } + + @Override + public List getSubProtocols() + { + return upgradeRequest.getSubProtocols(); + } + + @Override + public Principal getUserPrincipal() + { + return upgradeRequest.getUserPrincipal(); + } + + @Override + public boolean hasSubProtocol(String subprotocol) + { + return upgradeRequest.hasSubProtocol(subprotocol); + } + + @Override + public boolean isSecure() + { + return upgradeRequest.isSecure(); + } + + @Override + public X509Certificate[] getCertificates() + { + return upgradeRequest.getCertificates(); + } + + @Override + public HttpServletRequest getHttpServletRequest() + { + return upgradeRequest.getHttpServletRequest(); + } + + @Override + public Locale getLocale() + { + return upgradeRequest.getLocale(); + } + + @Override + public Enumeration getLocales() + { + return upgradeRequest.getLocales(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return upgradeRequest.getLocalSocketAddress(); + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return upgradeRequest.getRemoteSocketAddress(); + } + + @Override + public String getRequestPath() + { + return upgradeRequest.getRequestPath(); + } + + @Override + public Object getServletAttribute(String name) + { + return upgradeRequest.getServletAttribute(name); + } + + @Override + public Map getServletAttributes() + { + return upgradeRequest.getServletAttributes(); + } + + @Override + public Map> getServletParameters() + { + return upgradeRequest.getServletParameters(); + } + + @Override + public boolean isUserInRole(String role) + { + return upgradeRequest.isUserInRole(role); + } + + @Override + public void setServletAttribute(String name, Object value) + { + upgradeRequest.setServletAttribute(name, value); + } +} diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeResponse.java similarity index 53% rename from jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java rename to jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeResponse.java index 586d78e91c1..e72af1202aa 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseAdapter.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/DelegatedServerUpgradeResponse.java @@ -24,100 +24,115 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.common.JettyExtensionConfig; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; -public class UpgradeResponseAdapter implements UpgradeResponse +public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse { - private final ServletUpgradeResponse servletResponse; + private final ServerUpgradeResponse upgradeResponse; - public UpgradeResponseAdapter(ServletUpgradeResponse servletResponse) + public DelegatedServerUpgradeResponse(ServerUpgradeResponse response) { - this.servletResponse = servletResponse; + upgradeResponse = response; } @Override public void addHeader(String name, String value) { - this.servletResponse.addHeader(name, value); - } - - @Override - public String getAcceptedSubProtocol() - { - return this.servletResponse.getAcceptedSubProtocol(); - } - - @Override - public List getExtensions() - { - return this.servletResponse.getExtensions().stream() - .map((ext) -> new JettyExtensionConfig(ext.getName(), ext.getParameters())) - .collect(Collectors.toList()); - } - - @Override - public String getHeader(String name) - { - return this.servletResponse.getHeader(name); - } - - @Override - public Set getHeaderNames() - { - return this.servletResponse.getHeaderNames(); - } - - @Override - public Map> getHeaders() - { - return this.servletResponse.getHeadersMap(); - } - - @Override - public List getHeaders(String name) - { - return this.servletResponse.getHeaders(name); - } - - @Override - public int getStatusCode() - { - return this.servletResponse.getStatusCode(); - } - - @Override - public void sendForbidden(String message) throws IOException - { - this.servletResponse.sendForbidden(message); - } - - @Override - public void setAcceptedSubProtocol(String protocol) - { - this.servletResponse.setAcceptedSubProtocol(protocol); - } - - @Override - public void setExtensions(List extensions) - { - List coreExtensionConfigs = extensions.stream() - .map((ext) -> new org.eclipse.jetty.websocket.core.ExtensionConfig(ext.getName(), ext.getParameters())) - .collect(Collectors.toList()); - this.servletResponse.setExtensions(coreExtensionConfigs); + upgradeResponse.addHeader(name, value); } @Override public void setHeader(String name, String value) { - this.servletResponse.setHeader(name, value); + upgradeResponse.setHeader(name, value); + } + + @Override + public void setHeader(String name, List values) + { + upgradeResponse.setHeader(name, values); + } + + @Override + public String getAcceptedSubProtocol() + { + return upgradeResponse.getAcceptedSubProtocol(); + } + + @Override + public List getExtensions() + { + return upgradeResponse.getExtensions().stream().map(JettyExtensionConfig::new).collect(Collectors.toList()); + } + + @Override + public String getHeader(String name) + { + return upgradeResponse.getHeader(name); + } + + @Override + public Set getHeaderNames() + { + return upgradeResponse.getHeaderNames(); + } + + @Override + public Map> getHeaders() + { + return upgradeResponse.getHeadersMap(); + } + + @Override + public List getHeaders(String name) + { + return upgradeResponse.getHeaders(name); + } + + @Override + public int getStatusCode() + { + return upgradeResponse.getStatusCode(); + } + + @Override + public void sendForbidden(String message) throws IOException + { + upgradeResponse.sendForbidden(message); + } + + @Override + public void setAcceptedSubProtocol(String protocol) + { + upgradeResponse.setAcceptedSubProtocol(protocol); + } + + @Override + public void setExtensions(List configs) + { + upgradeResponse.setExtensions(configs.stream() + .map(c -> new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters())) + .collect(Collectors.toList())); } @Override public void setStatusCode(int statusCode) { - this.servletResponse.setStatusCode(statusCode); + upgradeResponse.setStatusCode(statusCode); + } + + @Override + public boolean isCommitted() + { + return upgradeResponse.isCommitted(); + } + + @Override + public void sendError(int statusCode, String message) throws IOException + { + upgradeResponse.sendError(statusCode, message); } } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java index ce4d9989c64..b7e594aedb1 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java @@ -28,12 +28,10 @@ import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeRequest; +import org.eclipse.jetty.websocket.util.server.internal.ServerUpgradeResponse; -public class JettyServerFrameHandlerFactory - extends JettyWebSocketFrameHandlerFactory - implements FrameHandlerFactory, LifeCycle.Listener +public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFactory implements FrameHandlerFactory, LifeCycle.Listener { public static JettyServerFrameHandlerFactory getFactory(ServletContext context) { @@ -47,11 +45,11 @@ public class JettyServerFrameHandlerFactory } @Override - public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse) + public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse) { JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo); - frameHandler.setUpgradeRequest(new UpgradeRequestAdapter(upgradeRequest)); - frameHandler.setUpgradeResponse(new UpgradeResponseAdapter(upgradeResponse)); + frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest)); + frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse)); return frameHandler; } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java deleted file mode 100644 index bddf712cf15..00000000000 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestAdapter.java +++ /dev/null @@ -1,217 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.server.internal; - -import java.net.HttpCookie; -import java.net.URI; -import java.security.Principal; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.eclipse.jetty.websocket.api.UpgradeRequest; -import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; -import org.eclipse.jetty.websocket.common.JettyExtensionConfig; -import org.eclipse.jetty.websocket.util.server.internal.ServletUpgradeRequest; - -public class UpgradeRequestAdapter implements UpgradeRequest -{ - private final ServletUpgradeRequest servletRequest; - - public UpgradeRequestAdapter(ServletUpgradeRequest servletRequest) - { - this.servletRequest = servletRequest; - } - - @Override - public void addExtensions(ExtensionConfig... configs) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void addExtensions(String... configs) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public List getCookies() - { - return this.servletRequest.getCookies(); - } - - @Override - public List getExtensions() - { - return this.servletRequest.getExtensions().stream() - .map((ext) -> new JettyExtensionConfig(ext.getName(), ext.getParameters())) - .collect(Collectors.toList()); - } - - @Override - public String getHeader(String name) - { - return this.servletRequest.getHeader(name); - } - - @Override - public int getHeaderInt(String name) - { - return this.servletRequest.getHeaderInt(name); - } - - @Override - public Map> getHeaders() - { - return this.servletRequest.getHeadersMap(); - } - - @Override - public List getHeaders(String name) - { - return this.servletRequest.getHeaders(name); - } - - @Override - public String getHost() - { - return this.servletRequest.getHost(); - } - - @Override - public String getHttpVersion() - { - return this.servletRequest.getHttpVersion(); - } - - @Override - public String getMethod() - { - return this.servletRequest.getMethod(); - } - - @Override - public String getOrigin() - { - return this.servletRequest.getOrigin(); - } - - @Override - public Map> getParameterMap() - { - return this.servletRequest.getParameterMap(); - } - - @Override - public String getProtocolVersion() - { - return this.servletRequest.getProtocolVersion(); - } - - @Override - public String getQueryString() - { - return this.servletRequest.getQueryString(); - } - - @Override - public URI getRequestURI() - { - return this.servletRequest.getRequestURI(); - } - - @Override - public Object getSession() - { - return this.servletRequest.getSession(); - } - - @Override - public List getSubProtocols() - { - return this.servletRequest.getSubProtocols(); - } - - @Override - public Principal getUserPrincipal() - { - return this.servletRequest.getUserPrincipal(); - } - - @Override - public boolean hasSubProtocol(String test) - { - return this.servletRequest.hasSubProtocol(test); - } - - @Override - public boolean isSecure() - { - return this.servletRequest.isSecure(); - } - - @Override - public void setCookies(List cookies) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setExtensions(List configs) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setHeader(String name, List values) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setHeader(String name, String value) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setHeaders(Map> headers) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setSession(Object session) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setSubProtocols(List protocols) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } - - @Override - public void setSubProtocols(String... protocols) - { - throw new UnsupportedOperationException("Not supported from Servlet API"); - } -} diff --git a/jetty-websocket/websocket-jetty-tests/pom.xml b/jetty-websocket/websocket-jetty-tests/pom.xml index b59c57ef428..745446b9349 100644 --- a/jetty-websocket/websocket-jetty-tests/pom.xml +++ b/jetty-websocket/websocket-jetty-tests/pom.xml @@ -73,6 +73,12 @@ jetty-slf4j-impl test
+ + org.eclipse.jetty + jetty-jmx + ${project.version} + test + diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GracefulCloseTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GracefulCloseTest.java new file mode 100644 index 00000000000..d3157d0f283 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GracefulCloseTest.java @@ -0,0 +1,114 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GracefulCloseTest +{ + private final EventSocket serverEndpoint = new EchoSocket(); + private Server server; + private URI serverUri; + private WebSocketClient client; + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + server.setHandler(contextHandler); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addMapping("/", ((req, resp) -> serverEndpoint))); + server.start(); + serverUri = WSURI.toWebsocket(server.getURI()); + + // StopTimeout is necessary for the websocket server sessions to gracefully close. + server.setStopTimeout(1000); + + client = new WebSocketClient(); + client.setStopTimeout(1000); + client.start(); + } + + @AfterEach + public void after() throws Exception + { + client.stop(); + server.stop(); + } + + @Test + public void testClientStop() throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connect(clientEndpoint, serverUri).get(5, TimeUnit.SECONDS); + + client.stop(); + + // Check that the client endpoint was closed with the correct status code and no error. + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeCode, is(StatusCode.SHUTDOWN)); + assertNull(clientEndpoint.error); + + // Check that the server endpoint was closed with the correct status code and no error. + assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(serverEndpoint.closeCode, is(StatusCode.SHUTDOWN)); + assertNull(serverEndpoint.error); + } + + @Test + public void testServerStop() throws Exception + { + EventSocket clientEndpoint = new EventSocket(); + client.connect(clientEndpoint, serverUri).get(5, TimeUnit.SECONDS); + + server.stop(); + + // Check that the client endpoint was closed with the correct status code and no error. + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeCode, is(StatusCode.SHUTDOWN)); + assertNull(clientEndpoint.error); + + // Check that the server endpoint was closed with the correct status code and no error. + assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(serverEndpoint.closeCode, is(StatusCode.SHUTDOWN)); + assertNull(serverEndpoint.error); + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java index 488eb796a9b..e1786c196b8 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -34,6 +35,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class JettyWebSocketFilterTest @@ -41,6 +43,7 @@ public class JettyWebSocketFilterTest private Server server; private ServerConnector connector; private WebSocketClient client; + private ServletContextHandler contextHandler; @BeforeEach public void start() throws Exception @@ -49,12 +52,11 @@ public class JettyWebSocketFilterTest connector = new ServerConnector(server); server.addConnector(connector); - ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); contextHandler.setContextPath("/"); server.setHandler(contextHandler); - JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> - container.addMapping("/", (req, resp) -> new EchoSocket())); + JettyWebSocketServletContainerInitializer.configure(contextHandler, null); server.start(); client = new WebSocketClient(); @@ -69,8 +71,20 @@ public class JettyWebSocketFilterTest } @Test - public void test() throws Exception + public void testLazyWebSocketUpgradeFilter() throws Exception { + // JettyWebSocketServerContainer has already been created. + JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(contextHandler.getServletContext()); + assertNotNull(container); + + // We should have no WebSocketUpgradeFilter installed because we have added no mappings. + assertThat(contextHandler.getServletHandler().getFilters().length, is(0)); + + // After mapping is added we have an UpgradeFilter. + container.addMapping("/", EchoSocket.class); + assertThat(contextHandler.getServletHandler().getFilters().length, is(1)); + + // Test we can upgrade to websocket and send a message. URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath"); EventSocket socket = new EventSocket(); CompletableFuture connect = client.connect(socket, uri); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java index b13097aaea9..3b9c4960357 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.tests; +import java.lang.management.ManagementFactory; import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; @@ -25,20 +26,19 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.ConnectionStatistics; -import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.io.IncludeExcludeConnectionStatistics; +import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.internal.Generator; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; -import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; -import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -50,52 +50,50 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class WebSocketStatsTest { - public static class MyWebSocketServlet extends JettyWebSocketServlet - { - @Override - public void configure(JettyWebSocketServletFactory factory) - { - factory.setAutoFragment(false); - factory.addMapping("/", (req, resp) -> new EchoSocket()); - } - } - + private final CountDownLatch wsConnectionClosed = new CountDownLatch(1); private Server server; private ServerConnector connector; private WebSocketClient client; - private ConnectionStatistics statistics; - private CountDownLatch wsUpgradeComplete = new CountDownLatch(1); - private CountDownLatch wsConnectionClosed = new CountDownLatch(1); + private IncludeExcludeConnectionStatistics statistics; @BeforeEach public void start() throws Exception { - statistics = new ConnectionStatistics() + statistics = new IncludeExcludeConnectionStatistics(); + statistics.include(WebSocketConnection.class); + + Connection.Listener.Adapter wsCloseListener = new Connection.Listener.Adapter() { @Override public void onClosed(Connection connection) { - super.onClosed(connection); - if (connection instanceof WebSocketConnection) wsConnectionClosed.countDown(); - else if (connection instanceof HttpConnection) - wsUpgradeComplete.countDown(); } }; server = new Server(); connector = new ServerConnector(server); connector.addBean(statistics); + connector.addBean(wsCloseListener); server.addConnector(connector); ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); - contextHandler.setContextPath("/"); - contextHandler.addServlet(MyWebSocketServlet.class, "/testPath"); server.setHandler(contextHandler); + contextHandler.setContextPath("/"); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + container.setAutoFragment(false); + container.addMapping("/", EchoSocket.class); + }); JettyWebSocketServletContainerInitializer.configure(contextHandler, null); client = new WebSocketClient(); + client.setAutoFragment(false); + + // Setup JMX. + MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); + server.addBean(mbeanContainer); server.start(); client.start(); @@ -104,8 +102,8 @@ public class WebSocketStatsTest @AfterEach public void stop() throws Exception { - client.stop(); server.stop(); + client.stop(); } long getFrameByteSize(Frame frame) @@ -119,22 +117,14 @@ public class WebSocketStatsTest @Test public void echoStatsTest() throws Exception { - URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/testPath"); + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/"); EventSocket socket = new EventSocket(); CompletableFuture connect = client.connect(socket, uri); final long numMessages = 1000; final String msgText = "hello world"; - - long upgradeSentBytes; - long upgradeReceivedBytes; - try (Session session = connect.get(5, TimeUnit.SECONDS)) { - wsUpgradeComplete.await(5, TimeUnit.SECONDS); - upgradeSentBytes = statistics.getSentBytes(); - upgradeReceivedBytes = statistics.getReceivedBytes(); - for (int i = 0; i < numMessages; i++) { session.getRemote().sendString(msgText); @@ -146,18 +136,18 @@ public class WebSocketStatsTest assertThat(statistics.getConnectionsMax(), is(1L)); assertThat(statistics.getConnections(), is(0L)); - assertThat(statistics.getSentMessages(), is(numMessages + 2L)); - assertThat(statistics.getReceivedMessages(), is(numMessages + 2L)); + // Sent and r eceived all of the echo messages + 1 for the close frame. + assertThat(statistics.getSentMessages(), is(numMessages + 1L)); + assertThat(statistics.getReceivedMessages(), is(numMessages + 1L)); Frame textFrame = new Frame(OpCode.TEXT, msgText); - Frame closeFrame = new Frame(OpCode.CLOSE); - + Frame closeFrame = CloseStatus.NORMAL_STATUS.toFrame(); final long textFrameSize = getFrameByteSize(textFrame); final long closeFrameSize = getFrameByteSize(closeFrame); final int maskSize = 4; // We use 4 byte mask for client frames in WSConnection - final long expectedSent = upgradeSentBytes + numMessages * textFrameSize + closeFrameSize; - final long expectedReceived = upgradeReceivedBytes + numMessages * (textFrameSize + maskSize) + closeFrameSize + maskSize; + final long expectedSent = numMessages * textFrameSize + closeFrameSize; + final long expectedReceived = numMessages * (textFrameSize + maskSize) + (closeFrameSize + maskSize); assertThat(statistics.getSentBytes(), is(expectedSent)); assertThat(statistics.getReceivedBytes(), is(expectedReceived)); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java index ba3446931d6..8b3a47e5bbb 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java @@ -159,7 +159,7 @@ public class ClientConfigTest assertNull(clientEndpoint.error); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(serverSocket.closeCode, is(StatusCode.NO_CODE)); + assertThat(serverSocket.closeCode, is(StatusCode.NORMAL)); } @ParameterizedTest diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java new file mode 100644 index 00000000000..f4b2345683c --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java @@ -0,0 +1,381 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.client; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import org.eclipse.jetty.client.HttpRequest; +import org.eclipse.jetty.client.HttpResponse; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.UpgradeException; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketSessionListener; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.JettyUpgradeListener; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; +import org.eclipse.jetty.websocket.tests.EchoSocket; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ConnectFutureTest +{ + private Server server; + private WebSocketClient client; + + public void start(Consumer configuration) throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + server.setHandler(contextHandler); + + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + configuration.accept(container)); + server.start(); + + client = new WebSocketClient(); + client.start(); + } + + @AfterEach + public void stop() throws Exception + { + if (client != null) + client.stop(); + if (server != null) + server.stop(); + } + + @Test + public void testAbortDuringCreator() throws Exception + { + CountDownLatch enteredCreator = new CountDownLatch(1); + CountDownLatch exitCreator = new CountDownLatch(1); + start(c -> + { + c.addMapping("/", (req, res) -> + { + try + { + enteredCreator.countDown(); + exitCreator.await(); + return new EchoSocket(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + }); + }); + + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + + // Cancel the future once we have entered the servers WebSocketCreator (after upgrade request is sent). + assertTrue(enteredCreator.await(5, TimeUnit.SECONDS)); + assertTrue(connect.cancel(true)); + assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); + exitCreator.countDown(); + assertFalse(clientSocket.openLatch.await(1, TimeUnit.SECONDS)); + + Throwable error = clientSocket.error.get(); + assertThat(error, instanceOf(UpgradeException.class)); + Throwable cause = error.getCause(); + assertThat(cause, instanceOf(org.eclipse.jetty.websocket.core.exception.UpgradeException.class)); + assertThat(cause.getCause(), instanceOf(CancellationException.class)); + } + + @Test + public void testAbortSessionOnCreated() throws Exception + { + start(c -> c.addMapping("/", EchoSocket.class)); + + CountDownLatch enteredListener = new CountDownLatch(1); + CountDownLatch exitListener = new CountDownLatch(1); + client.addSessionListener(new WebSocketSessionListener() + { + @Override + public void onWebSocketSessionCreated(Session session) + { + try + { + enteredListener.countDown(); + exitListener.await(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + } + }); + + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + + // Abort when session is created, this is during the connection upgrade. + assertTrue(enteredListener.await(5, TimeUnit.SECONDS)); + assertTrue(connect.cancel(true)); + assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); + exitListener.countDown(); + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientSocket.error.get(), instanceOf(CancellationException.class)); + } + + @Test + public void testAbortInHandshakeResponse() throws Exception + { + start(c -> c.addMapping("/", EchoSocket.class)); + + CountDownLatch enteredListener = new CountDownLatch(1); + CountDownLatch exitListener = new CountDownLatch(1); + JettyUpgradeListener upgradeListener = new JettyUpgradeListener() + { + @Override + public void onHandshakeResponse(HttpRequest request, HttpResponse response) + { + try + { + enteredListener.countDown(); + exitListener.await(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + } + }; + + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()), upgradeRequest, upgradeListener); + + // Abort after after handshake response, this is during the connection upgrade. + assertTrue(enteredListener.await(5, TimeUnit.SECONDS)); + assertTrue(connect.cancel(true)); + assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); + exitListener.countDown(); + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientSocket.error.get(), instanceOf(CancellationException.class)); + } + + @Test + public void testAbortOnOpened() throws Exception + { + start(c -> c.addMapping("/", EchoSocket.class)); + + CountDownLatch exitOnOpen = new CountDownLatch(1); + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint() + { + @Override + public void onWebSocketConnect(Session session) + { + try + { + super.onWebSocketConnect(session); + exitOnOpen.await(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + } + }; + + // Abort during the call to onOpened. This is after the connection upgrade, but before future completion. + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(connect.cancel(true)); + exitOnOpen.countDown(); + + // We got an error on the WebSocket endpoint and an error from the future. + assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); + assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); + } + + @Test + public void testAbortAfterCompletion() throws Exception + { + start(c -> c.addMapping("/", EchoSocket.class)); + + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + Session session = connect.get(5, TimeUnit.SECONDS); + + // If we can send and receive messages the future has been completed. + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + clientSocket.getSession().getRemote().sendString("hello"); + assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), Matchers.is("hello")); + + // After it has been completed we should not get any errors from cancelling it. + assertFalse(connect.cancel(true)); + assertThat(connect.get(5, TimeUnit.SECONDS), instanceOf(Session.class)); + assertFalse(clientSocket.closeLatch.await(1, TimeUnit.SECONDS)); + assertNull(clientSocket.error.get()); + + // Close the session properly. + session.close(); + assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientSocket.closeCode, is(StatusCode.NORMAL)); + } + + @Test + public void testFutureTimeout() throws Exception + { + CountDownLatch exitCreator = new CountDownLatch(1); + start(c -> + { + c.addMapping("/", (req, res) -> + { + try + { + exitCreator.await(); + return new EchoSocket(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + }); + }); + + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + assertThrows(TimeoutException.class, () -> connect.get(1, TimeUnit.SECONDS)); + exitCreator.countDown(); + Session session = connect.get(5, TimeUnit.SECONDS); + + // Close the session properly. + session.close(); + assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientSocket.closeCode, is(StatusCode.NORMAL)); + } + + @Test + public void testAbortWithExceptionBeforeUpgrade() throws Exception + { + CountDownLatch exitCreator = new CountDownLatch(1); + start(c -> + { + c.addMapping("/", (req, res) -> + { + try + { + exitCreator.await(); + return new EchoSocket(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + }); + }); + + // Complete the CompletableFuture with an exception the during the call to onOpened. + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint(); + CompletableFuture connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + assertTrue(connect.completeExceptionally(new WebSocketException("custom exception"))); + exitCreator.countDown(); + + // Exception from the future is correct. + ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS)); + Throwable futureCause = futureError.getCause(); + assertThat(futureCause, instanceOf(WebSocketException.class)); + assertThat(futureCause.getMessage(), is("custom exception")); + + // Exception from the endpoint is correct. + assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); + Throwable upgradeException = clientSocket.error.get(); + assertThat(upgradeException, instanceOf(UpgradeException.class)); + Throwable coreUpgradeException = upgradeException.getCause(); + assertThat(coreUpgradeException, instanceOf(org.eclipse.jetty.websocket.core.exception.UpgradeException.class)); + Throwable cause = coreUpgradeException.getCause(); + assertThat(cause, instanceOf(WebSocketException.class)); + assertThat(cause.getMessage(), is("custom exception")); + } + + @Test + public void testAbortWithExceptionAfterUpgrade() throws Exception + { + start(c -> c.addMapping("/", EchoSocket.class)); + CountDownLatch exitOnOpen = new CountDownLatch(1); + CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint() + { + @Override + public void onWebSocketConnect(Session session) + { + try + { + super.onWebSocketConnect(session); + exitOnOpen.await(); + } + catch (InterruptedException e) + { + throw new IllegalStateException(e); + } + } + }; + + // Complete the CompletableFuture with an exception the during the call to onOpened. + CompletableFuture connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); + assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(connect.completeExceptionally(new WebSocketException("custom exception"))); + exitOnOpen.countDown(); + + // Exception from the future is correct. + ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS)); + Throwable cause = futureError.getCause(); + assertThat(cause, instanceOf(WebSocketException.class)); + assertThat(cause.getMessage(), is("custom exception")); + + // Exception from the endpoint is correct. + assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); + Throwable endpointError = clientSocket.error.get(); + assertThat(endpointError, instanceOf(WebSocketException.class)); + assertThat(endpointError.getMessage(), is("custom exception")); + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java index fe290ccba82..09050619ea2 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.websocket.tests.listeners; import java.net.URI; import java.nio.ByteBuffer; import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -28,13 +30,18 @@ import java.util.stream.Stream; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.tests.EchoSocket; import org.eclipse.jetty.websocket.tests.EventSocket; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -60,6 +67,8 @@ public class WebSocketListenerTest contextHandler.setContextPath("/"); JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> { + container.addMapping("/echo", (req, res) -> new EchoSocket()); + for (Class c : getClassListFromArguments(TextListeners.getTextListeners())) { container.addMapping("/text/" + c.getSimpleName(), (req, res) -> construct(c)); @@ -125,6 +134,47 @@ public class WebSocketListenerTest assertThat(clientEndpoint.closeReason, is("standard close")); } + @Test + public void testAnonymousListener() throws Exception + { + CountDownLatch openLatch = new CountDownLatch(1); + CountDownLatch closeLatch = new CountDownLatch(1); + BlockingQueue textMessages = new BlockingArrayQueue<>(); + WebSocketListener clientEndpoint = new WebSocketListener() + { + @Override + public void onWebSocketConnect(Session session) + { + openLatch.countDown(); + } + + @Override + public void onWebSocketText(String message) + { + textMessages.add(message); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) + { + closeLatch.countDown(); + } + }; + + Session session = client.connect(clientEndpoint, serverUri.resolve("/echo")).get(5, TimeUnit.SECONDS); + assertTrue(openLatch.await(5, TimeUnit.SECONDS)); + + // Send and receive echo on client. + String payload = "hello world"; + session.getRemote().sendString(payload); + String echoMessage = textMessages.poll(5, TimeUnit.SECONDS); + assertThat(echoMessage, is(payload)); + + // Close normally. + session.close(StatusCode.NORMAL, "standard close"); + assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + } + private List> getClassListFromArguments(Stream stream) { return stream.map(arguments -> (Class)arguments.get()[0]).collect(Collectors.toList()); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java index 739877c084e..6e30ef30f8e 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java @@ -230,7 +230,7 @@ public class ServerConfigTest assertNull(serverEndpoint.error); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(clientEndpoint.closeCode, is(StatusCode.NO_CODE)); + assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); listener.assertClosed(); } diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java index bebcf7fe203..1eb7c367fee 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/FrameHandlerFactory.java @@ -34,6 +34,5 @@ public interface FrameHandlerFactory * @return the API specific FrameHandler, or null if this implementation is unable to create * the FrameHandler (allowing another {@link FrameHandlerFactory} to try) */ - FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, - ServletUpgradeResponse upgradeResponse); + FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse); } diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeRequest.java similarity index 98% rename from jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeRequest.java index a4418a9e6e7..81a9b31e98f 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeRequest.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeRequest.java @@ -46,7 +46,7 @@ import org.eclipse.jetty.websocket.core.server.Negotiation; /** * Holder of request data for a WebSocket upgrade request. */ -public class ServletUpgradeRequest +public class ServerUpgradeRequest { private final URI requestURI; private final String queryString; @@ -56,7 +56,7 @@ public class ServletUpgradeRequest private List cookies; private Map> parameterMap; - public ServletUpgradeRequest(Negotiation negotiation) throws BadMessageException + public ServerUpgradeRequest(Negotiation negotiation) throws BadMessageException { this.negotiation = negotiation; HttpServletRequest httpRequest = negotiation.getRequest(); diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeResponse.java similarity index 98% rename from jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java rename to jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeResponse.java index f78615f46c6..73095906258 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServletUpgradeResponse.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/ServerUpgradeResponse.java @@ -36,12 +36,12 @@ import org.eclipse.jetty.websocket.core.server.Negotiation; /** * Servlet Specific UpgradeResponse implementation. */ -public class ServletUpgradeResponse +public class ServerUpgradeResponse { private final HttpServletResponse response; private final Negotiation negotiation; - public ServletUpgradeResponse(Negotiation negotiation) + public ServerUpgradeResponse(Negotiation negotiation) { this.negotiation = negotiation; this.response = negotiation.getResponse(); diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java index 72eafaff707..93482fcc5a5 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/UpgradeHttpServletRequest.java @@ -81,7 +81,7 @@ public class UpgradeHttpServletRequest implements HttpServletRequest private final Map attributes = new HashMap<>(2); private final List locales = new ArrayList<>(2); - private HttpSession session; + private final HttpSession session; private final InetSocketAddress localAddress; private final String localName; diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java index b991530193f..9479a66d367 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketCreator.java @@ -34,5 +34,5 @@ public interface WebSocketCreator * @param resp the response details * @return a websocket object to use, or null if no websocket should be created from this request. */ - Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp); + Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp); } diff --git a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java index d76cbd53e9f..49283dbf8e1 100644 --- a/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java +++ b/jetty-websocket/websocket-util-server/src/main/java/org/eclipse/jetty/websocket/util/server/internal/WebSocketMapping.java @@ -265,8 +265,8 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener if (servletContext == null) throw new IllegalStateException("null servletContext from request"); - ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation); - ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation); + ServerUpgradeRequest upgradeRequest = new ServerUpgradeRequest(negotiation); + ServerUpgradeResponse upgradeResponse = new ServerUpgradeResponse(negotiation); AtomicReference result = new AtomicReference<>(); ((ContextHandler.Context)servletContext).getContextHandler().handle(() -> diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/InvokerUtils.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/InvokerUtils.java index c09827cc608..8a3bc181b80 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/InvokerUtils.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/InvokerUtils.java @@ -26,6 +26,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class InvokerUtils { public static class Arg @@ -135,6 +138,7 @@ public class InvokerUtils } public static final ParamIdentifier PARAM_IDENTITY = new ParamIdentity(); + private static final Logger LOG = LoggerFactory.getLogger(InvokerUtils.class); /** * Bind optional arguments to provided method handle @@ -425,6 +429,8 @@ public class InvokerUtils { if (!throwOnFailure) { + if (LOG.isDebugEnabled()) + LOG.debug("Unable to obtain MethodHandle for " + method, e); return null; } diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageInputStream.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageInputStream.java index 54ed65b55bd..c3bc6986769 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageInputStream.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageInputStream.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.websocket.core.Frame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +45,8 @@ public class MessageInputStream extends InputStream implements MessageSink private static final Logger LOG = LoggerFactory.getLogger(MessageInputStream.class); private static final Entry EOF = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP); private static final Entry CLOSED = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP); + + private final AutoLock lock = new AutoLock(); private final BlockingArrayQueue buffers = new BlockingArrayQueue<>(); private boolean closed = false; private Entry currentEntry; @@ -56,7 +59,7 @@ public class MessageInputStream extends InputStream implements MessageSink LOG.debug("accepting {}", frame); boolean succeed = false; - synchronized (this) + try (AutoLock l = lock.lock()) { // If closed or we have no payload, request the next frame. if (closed || (!frame.hasPayload() && !frame.isFin())) @@ -134,7 +137,7 @@ public class MessageInputStream extends InputStream implements MessageSink LOG.debug("close()"); ArrayList entries = new ArrayList<>(); - synchronized (this) + try (AutoLock l = lock.lock()) { if (closed) return; @@ -169,7 +172,7 @@ public class MessageInputStream extends InputStream implements MessageSink private void succeedCurrentEntry() { Entry current; - synchronized (this) + try (AutoLock l = lock.lock()) { current = currentEntry; currentEntry = null; @@ -180,7 +183,7 @@ public class MessageInputStream extends InputStream implements MessageSink private Entry getCurrentEntry() throws IOException { - synchronized (this) + try (AutoLock l = lock.lock()) { if (currentEntry != null) return currentEntry; @@ -205,7 +208,7 @@ public class MessageInputStream extends InputStream implements MessageSink throw new IOException(String.format("Read timeout: %,dms expired", timeoutMs)); } - synchronized (this) + try (AutoLock l = lock.lock()) { currentEntry = result; return currentEntry; diff --git a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageOutputStream.java b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageOutputStream.java index 389fd20eaf5..e3b841bc05e 100644 --- a/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageOutputStream.java +++ b/jetty-websocket/websocket-util/src/main/java/org/eclipse/jetty/websocket/util/messages/MessageOutputStream.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; @@ -39,6 +40,7 @@ public class MessageOutputStream extends OutputStream { private static final Logger LOG = LoggerFactory.getLogger(MessageOutputStream.class); + private final AutoLock lock = new AutoLock(); private final CoreSession coreSession; private final ByteBufferPool bufferPool; private final int bufferSize; @@ -123,7 +125,7 @@ public class MessageOutputStream extends OutputStream private void flush(boolean fin) throws IOException { - synchronized (this) + try (AutoLock l = lock.lock()) { if (closed) throw new IOException("Stream is closed"); @@ -157,7 +159,7 @@ public class MessageOutputStream extends OutputStream private void send(ByteBuffer data) throws IOException { - synchronized (this) + try (AutoLock l = lock.lock()) { if (closed) throw new IOException("Stream is closed"); @@ -197,7 +199,7 @@ public class MessageOutputStream extends OutputStream public void setCallback(Callback callback) { - synchronized (this) + try (AutoLock l = lock.lock()) { this.callback = callback; } @@ -206,26 +208,22 @@ public class MessageOutputStream extends OutputStream private void notifySuccess() { Callback callback; - synchronized (this) + try (AutoLock l = lock.lock()) { callback = this.callback; } if (callback != null) - { callback.succeeded(); - } } private void notifyFailure(Throwable failure) { Callback callback; - synchronized (this) + try (AutoLock l = lock.lock()) { callback = this.callback; } if (callback != null) - { callback.failed(failure); - } } } 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 c3cca7ec114..199fc034f8e 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 @@ -63,6 +63,7 @@ import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; @@ -249,7 +250,7 @@ public class XmlConfiguration */ public XmlConfiguration(Resource resource) throws SAXException, IOException { - synchronized (PARSER) + try (AutoLock l = PARSER.lock()) { _location = resource; try (InputStream inputStream = resource.getInputStream()) diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java index 58f8da3c488..28f143861de 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java @@ -35,6 +35,7 @@ import javax.xml.parsers.SAXParserFactory; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.thread.AutoLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; @@ -56,6 +57,7 @@ public class XmlParser { private static final Logger LOG = LoggerFactory.getLogger(XmlParser.class); + private final AutoLock _lock = new AutoLock(); private Map _redirectMap = new HashMap(); private SAXParser _parser; private Map _observerMap; @@ -81,6 +83,11 @@ public class XmlParser setValidating(validating); } + AutoLock lock() + { + return _lock.lock(); + } + public void setValidating(boolean validating) { try @@ -127,10 +134,15 @@ public class XmlParser return _parser.isValidating(); } - public synchronized void redirectEntity(String name, URL entity) + public void redirectEntity(String name, URL entity) { if (entity != null) - _redirectMap.put(name, entity); + { + try (AutoLock l = _lock.lock()) + { + _redirectMap.put(name, entity); + } + } } /** @@ -170,29 +182,35 @@ public class XmlParser * @param trigger Tag local or q name. * @param observer SAX ContentHandler */ - public synchronized void addContentHandler(String trigger, ContentHandler observer) + public void addContentHandler(String trigger, ContentHandler observer) { - if (_observerMap == null) - _observerMap = new HashMap<>(); - _observerMap.put(trigger, observer); + try (AutoLock l = _lock.lock()) + { + if (_observerMap == null) + _observerMap = new HashMap<>(); + _observerMap.put(trigger, observer); + } } - public synchronized Node parse(InputSource source) throws IOException, SAXException + public Node parse(InputSource source) throws IOException, SAXException { - _dtd = null; - Handler handler = new Handler(); - XMLReader reader = _parser.getXMLReader(); - reader.setContentHandler(handler); - reader.setErrorHandler(handler); - reader.setEntityResolver(handler); - if (LOG.isDebugEnabled()) - LOG.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId()); - _parser.parse(source, handler); - if (handler._error != null) - throw handler._error; - Node doc = (Node)handler._top.get(0); - handler.clear(); - return doc; + try (AutoLock l = _lock.lock()) + { + _dtd = null; + Handler handler = new Handler(); + XMLReader reader = _parser.getXMLReader(); + reader.setContentHandler(handler); + reader.setErrorHandler(handler); + reader.setEntityResolver(handler); + if (LOG.isDebugEnabled()) + LOG.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId()); + _parser.parse(source, handler); + if (handler._error != null) + throw handler._error; + Node doc = (Node)handler._top.get(0); + handler.clear(); + return doc; + } } /** @@ -203,7 +221,7 @@ public class XmlParser * @throws IOException if unable to load the xml * @throws SAXException if unable to parse the xml */ - public synchronized Node parse(String url) throws IOException, SAXException + public Node parse(String url) throws IOException, SAXException { if (LOG.isDebugEnabled()) LOG.debug("parse: " + url); @@ -218,7 +236,7 @@ public class XmlParser * @throws IOException if unable to load the xml * @throws SAXException if unable to parse the xml */ - public synchronized Node parse(File file) throws IOException, SAXException + public Node parse(File file) throws IOException, SAXException { if (LOG.isDebugEnabled()) LOG.debug("parse: " + file); @@ -233,20 +251,9 @@ public class XmlParser * @throws IOException if unable to load the xml * @throws SAXException if unable to parse the xml */ - public synchronized Node parse(InputStream in) throws IOException, SAXException + public Node parse(InputStream in) throws IOException, SAXException { - _dtd = null; - Handler handler = new Handler(); - XMLReader reader = _parser.getXMLReader(); - reader.setContentHandler(handler); - reader.setErrorHandler(handler); - reader.setEntityResolver(handler); - _parser.parse(new InputSource(in), handler); - if (handler._error != null) - throw handler._error; - Node doc = (Node)handler._top.get(0); - handler.clear(); - return doc; + return parse(new InputSource(in)); } protected InputSource resolveEntity(String pid, String sid) @@ -676,7 +683,7 @@ public class XmlParser } @Override - public synchronized String toString() + public String toString() { return toString(true); } @@ -687,7 +694,7 @@ public class XmlParser * @param tag If false, only _content is shown. * @return the string value */ - public synchronized String toString(boolean tag) + public String toString(boolean tag) { StringBuilder buf = new StringBuilder(); toString(buf, tag); @@ -701,7 +708,7 @@ public class XmlParser * @param trim true to trim the content * @return the trimmed content */ - public synchronized String toString(boolean tag, boolean trim) + public String toString(boolean tag, boolean trim) { String s = toString(tag); if (s != null && trim) @@ -709,7 +716,7 @@ public class XmlParser return s; } - private synchronized void toString(StringBuilder buf, boolean tag) + private void toString(StringBuilder buf, boolean tag) { if (tag) { diff --git a/pom.xml b/pom.xml index 57a5c04740f..f67503a091e 100644 --- a/pom.xml +++ b/pom.xml @@ -44,15 +44,15 @@ -Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx2g -Xms2g -Xlog:gc:stderr:time,level,tags - 3.0.0-M4 + 3.0.0-M5 3.8.1 - 3.1.1 - 3.1.0 - 3.0.1 - 3.2.2 + 3.1.2 + 3.2.0 + 3.2.1 + 3.3.1 3.5.2 - 2.5.2 - 2.8.2 + 3.0.0-M1 + 3.0.0-M1 false diff --git a/tests/jetty-jmh/pom.xml b/tests/jetty-jmh/pom.xml index edf3878dfd9..200943c79f1 100644 --- a/tests/jetty-jmh/pom.xml +++ b/tests/jetty-jmh/pom.xml @@ -106,6 +106,11 @@ jetty-slf4j-impl test + + org.eclipse.jetty + jetty-client + ${project.version} + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/client/jmh/ConnectionPoolsBenchmark.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/client/jmh/ConnectionPoolsBenchmark.java new file mode 100644 index 00000000000..e728bd8d196 --- /dev/null +++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/client/jmh/ConnectionPoolsBenchmark.java @@ -0,0 +1,174 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.client.jmh; + +import java.net.URI; +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +import org.eclipse.jetty.client.ConnectionPool; +import org.eclipse.jetty.client.DuplexConnectionPool; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConversation; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpRequest; +import org.eclipse.jetty.client.MultiplexConnectionPool; +import org.eclipse.jetty.client.Origin; +import org.eclipse.jetty.client.RoundRobinConnectionPool; +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.util.Attachable; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@State(Scope.Benchmark) +public class ConnectionPoolsBenchmark +{ + private ConnectionPool pool; + + @Param({"round-robin", "cached/multiplex", "uncached/multiplex", "cached/duplex", "uncached/duplex"}) + public static String POOL_TYPE; + + @Setup + public void setUp() throws Exception + { + HttpClient httpClient = new HttpClient() + { + @Override + protected void newConnection(HttpDestination destination, Promise promise) + { + promise.succeeded(new MockConnection()); + } + }; + HttpDestination httpDestination = new HttpDestination(httpClient, new Origin("http", "localhost", 8080)) + { + }; + + HttpConversation httpConversation = new HttpConversation(); + HttpRequest httpRequest = new HttpRequest(httpClient, httpConversation, new URI("http://localhost:8080")) {}; + HttpExchange httpExchange = new HttpExchange(httpDestination, httpRequest, new ArrayList<>()); + httpDestination.getHttpExchanges().add(httpExchange); + + int initialConnections = 12; + int maxConnections = 100; + switch (POOL_TYPE) + { + case "uncached/duplex": + pool = new DuplexConnectionPool(httpDestination, maxConnections, false, Callback.NOOP); + pool.preCreateConnections(initialConnections).get(); + break; + case "cached/duplex": + pool = new DuplexConnectionPool(httpDestination, maxConnections, true, Callback.NOOP); + pool.preCreateConnections(initialConnections).get(); + break; + case "uncached/multiplex": + pool = new MultiplexConnectionPool(httpDestination, maxConnections,false, Callback.NOOP, 12); + pool.preCreateConnections(initialConnections).get(); + break; + case "cached/multiplex": + pool = new MultiplexConnectionPool(httpDestination, maxConnections,true, Callback.NOOP, 12); + pool.preCreateConnections(initialConnections).get(); + break; + case "round-robin": + pool = new RoundRobinConnectionPool(httpDestination, maxConnections, Callback.NOOP); + pool.preCreateConnections(maxConnections).get(); + break; + default: + throw new AssertionError("Unknown pool type: " + POOL_TYPE); + } + } + + @TearDown + public void tearDown() + { + pool.close(); + pool = null; + } + + @Benchmark + public void testPool() + { + Connection connection = pool.acquire(true); + if (connection == null && !POOL_TYPE.equals("round-robin")) + throw new AssertionError("from thread " + Thread.currentThread().getName()); + Blackhole.consumeCPU(ThreadLocalRandom.current().nextInt(10, 20)); + if (connection != null) + pool.release(connection); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(ConnectionPoolsBenchmark.class.getSimpleName()) + .warmupIterations(3) + .measurementIterations(3) + .forks(1) + .threads(12) + //.addProfiler(LinuxPerfProfiler.class) + .build(); + + new Runner(opt).run(); + } + + static class MockConnection implements Connection, Attachable + { + private Object attachment; + + @Override + public void close() + { + } + + @Override + public boolean isClosed() + { + return false; + } + + @Override + public void send(Request request, Response.CompleteListener listener) + { + } + + @Override + public void setAttachment(Object obj) + { + this.attachment = obj; + } + + @Override + public Object getAttachment() + { + return attachment; + } + } +} diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java index 1b6e8076480..46215aca691 100644 --- a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java +++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/jmh/DateCacheSimpleDateFormat.java @@ -23,6 +23,8 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; +import org.eclipse.jetty.util.thread.AutoLock; + /** * Date Format Cache. * Computes String representations of Dates and caches @@ -40,14 +42,13 @@ import java.util.TimeZone; */ public class DateCacheSimpleDateFormat { - public static final String DEFAULT_FORMAT = "EEE MMM dd HH:mm:ss zzz yyyy"; + private final AutoLock _lock = new AutoLock(); private final String _formatString; private final String _tzFormatString; private final SimpleDateFormat _tzFormat; private final Locale _locale; - private volatile Tick _tick; public static class Tick @@ -132,7 +133,9 @@ public class DateCacheSimpleDateFormat _tzFormatString = sb.toString(); } else + { _tzFormatString = _formatString; + } if (_locale != null) { @@ -168,7 +171,7 @@ public class DateCacheSimpleDateFormat if (tick == null || seconds != tick._seconds) { // It's a cache miss - synchronized (this) + try (AutoLock l = _lock.lock()) { return _tzFormat.format(inDate); } @@ -196,7 +199,7 @@ public class DateCacheSimpleDateFormat { // It's a cache miss Date d = new Date(inDate); - synchronized (this) + try (AutoLock l = _lock.lock()) { return _tzFormat.format(d); } @@ -241,7 +244,7 @@ public class DateCacheSimpleDateFormat long seconds = now / 1000; // Synchronize to protect _tzFormat - synchronized (this) + try (AutoLock l = _lock.lock()) { // recheck the tick, to save multiple formats if (_tick == null || _tick._seconds != seconds) diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java index cd09de2517a..84bb102795f 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DemoBaseTests.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.FormRequestContent; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -70,6 +71,7 @@ public class DemoBaseTests extends AbstractDistributionTest } @Test + @Tag("external") public void testAsyncRest() throws Exception { String jettyVersion = System.getProperty("jettyVersion"); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 6afc56b7c6e..38f729e84b2 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -467,7 +467,7 @@ public class DistributionTests extends AbstractDistributionTest assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message")); session.close(); assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE)); + assertThat(webSocketListener.closeCode, is(StatusCode.NORMAL)); // Verify that /test2 and /test3 could not be started. ContentResponse response = client.GET(serverUri.resolve("/test2/badonopen/a")); @@ -482,7 +482,7 @@ public class DistributionTests extends AbstractDistributionTest assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message")); session.close(); assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS)); - assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE)); + assertThat(webSocketListener.closeCode, is(StatusCode.NORMAL)); } } } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index a48de6bbf96..35c79d7b6a8 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http.client; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -48,10 +49,12 @@ import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -61,6 +64,7 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -678,6 +682,59 @@ public class HttpClientTest extends AbstractTest assertEquals(users, destinations.size()); } + @ParameterizedTest + @ArgumentsSource(TransportProvider.class) + public void testIPv6Host(Transport transport) throws Exception + { + Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); + + init(transport); + scenario.start(new EmptyServerHandler() + { + @Override + protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.setContentType("text/plain"); + response.getOutputStream().print(request.getHeader("Host")); + } + }); + + // Test with a full URI. + String hostAddress = "::1"; + String host = "[" + hostAddress + "]"; + int port = Integer.parseInt(scenario.getNetworkConnectorLocalPort().get()); + String uri = scenario.getScheme() + "://" + host + ":" + port + "/path"; + ContentResponse response = scenario.client.newRequest(uri) + .method(HttpMethod.PUT) + .timeout(5, TimeUnit.SECONDS) + .send(); + assertNotNull(response); + assertEquals(200, response.getStatus()); + assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); + + // Test with host address. + response = scenario.client.newRequest(hostAddress, port) + .scheme(scenario.getScheme()) + .method(HttpMethod.PUT) + .timeout(5, TimeUnit.SECONDS) + .send(); + assertNotNull(response); + assertEquals(200, response.getStatus()); + assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); + + // Test with host. + response = scenario.client.newRequest(host, port) + .scheme(scenario.getScheme()) + .method(HttpMethod.PUT) + .timeout(5, TimeUnit.SECONDS) + .send(); + assertNotNull(response); + assertEquals(200, response.getStatus()); + assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); + + assertEquals(1, scenario.client.getDestinations().size()); + } + private void sleep(long time) throws IOException { try diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java index 392e7eb74f2..264649db12e 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java @@ -57,6 +57,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.opentest4j.TestAbortedException; import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -507,7 +508,7 @@ public class HttpClientTimeoutTest extends AbstractTest // connect to them will hang the connection attempt, which is // what we want to simulate in this test. socket.connect(new InetSocketAddress(host, port), connectTimeout); - // Abort the test if we can connect. + // Fail the test if we can connect. fail("Error: Should not have been able to connect to " + host + ":" + port); } catch (SocketTimeoutException ignored) @@ -517,7 +518,7 @@ public class HttpClientTimeoutTest extends AbstractTest catch (Throwable x) { // Abort if any other exception happens. - fail(x); + throw new TestAbortedException("Not able to validate connect timeout conditions", x); } } diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java index be1a5bacc91..57b9390c9c9 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/KeyStoreScannerTest.java @@ -21,10 +21,10 @@ package org.eclipse.jetty.test; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.time.Duration; import java.util.Calendar; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; @@ -48,19 +48,22 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.extension.ExtendWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.condition.OS.WINDOWS; @ExtendWith(WorkDirExtension.class) public class KeyStoreScannerTest { - private static final int scanInterval = 1; public WorkDir testdir; private Server server; private Path keystoreDir; + private KeyStoreScanner keystoreScanner; @BeforeEach public void before() @@ -99,8 +102,8 @@ public class KeyStoreScannerTest server.addConnector(connector); // Configure Keystore Reload. - KeyStoreScanner keystoreScanner = new KeyStoreScanner(sslContextFactory); - keystoreScanner.setScanInterval(scanInterval); + keystoreScanner = new KeyStoreScanner(sslContextFactory); + keystoreScanner.setScanInterval(0); server.addBean(keystoreScanner); server.start(); @@ -123,7 +126,7 @@ public class KeyStoreScannerTest // Switch to use newKeystore which has a later expiry date. useKeystore("newKeystore"); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + keystoreScanner.scan(); // The scanner should have detected the updated keystore, expiry should be renewed. X509Certificate cert2 = getCertificateFromServer(); @@ -143,11 +146,11 @@ public class KeyStoreScannerTest try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class)) { useKeystore("badKeystore"); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + keystoreScanner.scan(); } // The good keystore is removed, now the bad keystore now causes an exception. - assertThrows(Throwable.class, () -> getCertificateFromServer()); + assertThrows(Throwable.class, this::getCertificateFromServer); } @Test @@ -162,21 +165,23 @@ public class KeyStoreScannerTest // Delete the keystore. try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class)) { - useKeystore(null); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + Path keystorePath = keystoreDir.resolve("keystore"); + assertTrue(Files.deleteIfExists(keystorePath)); + keystoreScanner.scan(); } // The good keystore is removed, having no keystore causes an exception. - assertThrows(Throwable.class, () -> getCertificateFromServer()); + assertThrows(Throwable.class, this::getCertificateFromServer); // Switch to use keystore2 which has a later expiry date. useKeystore("newKeystore"); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + keystoreScanner.scan(); X509Certificate cert2 = getCertificateFromServer(); assertThat(getExpiryYear(cert2), is(2020)); } @Test + @DisabledOnOs(WINDOWS) // does not support symbolic link public void testReloadChangingSymbolicLink() throws Exception { Path keystorePath = keystoreDir.resolve("symlinkKeystore"); @@ -195,7 +200,7 @@ public class KeyStoreScannerTest // Change the symlink to point to the newKeystore file location which has a later expiry date. Files.delete(keystorePath); Files.createSymbolicLink(keystorePath, useKeystore("newKeystore")); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + keystoreScanner.scan(); // The scanner should have detected the updated keystore, expiry should be renewed. X509Certificate cert2 = getCertificateFromServer(); @@ -203,13 +208,19 @@ public class KeyStoreScannerTest } @Test + @DisabledOnOs(WINDOWS) // does not support symbolic link public void testReloadChangingTargetOfSymbolicLink() throws Exception { + Path keystoreLink = keystoreDir.resolve("symlinkKeystore"); + Path oldKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("oldKeystore"); + Path newKeystoreSrc = MavenTestingUtils.getTestResourcePathFile("newKeystore"); + Path target = keystoreDir.resolve("keystore"); + start(sslContextFactory -> { - Path keystorePath = keystoreDir.resolve("symlinkKeystore"); - Files.createSymbolicLink(keystorePath, useKeystore("oldKeystore")); - sslContextFactory.setKeyStorePath(keystorePath.toString()); + Files.copy(oldKeystoreSrc, target); + Files.createSymbolicLink(keystoreLink, target); + sslContextFactory.setKeyStorePath(keystoreLink.toString()); sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyManagerPassword("keypwd"); }); @@ -219,8 +230,9 @@ public class KeyStoreScannerTest assertThat(getExpiryYear(cert1), is(2015)); // Change the target file of the symlink to the newKeystore which has a later expiry date. - useKeystore("newKeystore"); - Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); + Files.copy(newKeystoreSrc, target, StandardCopyOption.REPLACE_EXISTING); + System.err.println("### Triggering scan"); + keystoreScanner.scan(); // The scanner should have detected the updated keystore, expiry should be renewed. X509Certificate cert2 = getCertificateFromServer(); @@ -233,11 +245,7 @@ public class KeyStoreScannerTest if (Files.exists(keystorePath)) Files.delete(keystorePath); - if (keystore == null) - return null; - - Files.copy(MavenTestingUtils.getTestResourceFile(keystore).toPath(), keystorePath); - keystorePath.toFile().deleteOnExit(); + Files.copy(MavenTestingUtils.getTestResourcePath(keystore), keystorePath); if (!Files.exists(keystorePath)) throw new IllegalStateException("keystore file was not created"); @@ -261,6 +269,7 @@ public class KeyStoreScannerTest HttpsURLConnection connection = (HttpsURLConnection)serverUrl.openConnection(); connection.setHostnameVerifier((a, b) -> true); + connection.setRequestProperty("Connection", "close"); connection.connect(); Certificate[] certs = connection.getServerCertificates(); connection.disconnect(); diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java index 6bef5cd697c..b89d6f0bece 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/XmlBasedJettyServer.java @@ -160,7 +160,7 @@ public class XmlBasedJettyServer assertEquals(1, serverCount, "Server load count"); this._server = foundServer; - this._server.setStopTimeout(1000); + this._server.setStopTimeout(2000); } public String getScheme() diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java index 973b87446a5..6d2f20bcf84 100644 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestHelper.java @@ -36,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -54,14 +55,16 @@ public class MongoTestHelper static GenericContainer mongo = new GenericContainer("mongo:" + System.getProperty("mongo.docker.version", "2.2.7")) - .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG)); + .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG)) + .waitingFor(new LogMessageWaitStrategy() + .withRegEx(".*waiting for connections.*")); static MongoClient mongoClient; static String mongoHost; static int mongoPort; - public static void startMongo() + static { try { @@ -71,6 +74,7 @@ public class MongoTestHelper mongoPort = mongo.getMappedPort(27017); LOG.info("Mongo container started for {}:{} - {}ms", mongoHost, mongoPort, System.currentTimeMillis() - start); + mongoClient = new MongoClient(mongoHost, mongoPort); } catch (Exception e) { @@ -79,24 +83,8 @@ public class MongoTestHelper } } - public static void stopMongo() - { - mongo.stop(); - mongoClient = null; - } - public static MongoClient getMongoClient() throws UnknownHostException { - boolean restart = false; - if (mongo == null || !mongo.isRunning()) - { - startMongo(); - restart = true; - } - if (mongoClient == null || restart) - { - mongoClient = new MongoClient(mongoHost, mongoPort); - } return mongoClient; } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java index b8938aa1bae..cce49261271 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java @@ -255,9 +255,4 @@ public class DispatchServlet extends HttpServlet { return "Include Servlet"; } - - @Override - public synchronized void destroy() - { - } } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java index cda240ce815..8859cb69cb0 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java @@ -919,7 +919,7 @@ public class Dump extends HttpServlet } @Override - public synchronized void destroy() + public void destroy() { _timer.cancel(); } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java index 7db6bace9be..ca23b1ab532 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java @@ -164,11 +164,6 @@ public class RegTest extends HttpServlet return "Rego Servlet"; } - @Override - public synchronized void destroy() - { - } - private String notag(String s) { if (s == null) diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java index 86fdcca40df..67bd337b7c6 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java @@ -79,7 +79,7 @@ public class SessionDump extends HttpServlet session = request.getSession(true); session.setAttribute("test", "value"); session.setAttribute("obj", new ObjectAttributeValue(System.currentTimeMillis())); - session.setAttribute("WEBCL", new MultiMap()); + session.setAttribute("WEBCL", new MultiMap<>()); } else if (session != null) { @@ -137,7 +137,7 @@ public class SessionDump extends HttpServlet else { if (session.getAttribute("WEBCL") == null) - session.setAttribute("WEBCL", new MultiMap()); + session.setAttribute("WEBCL", new MultiMap<>()); try { out.println("ID: " + session.getId() + "
");