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-plugin2.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):