Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-4572-message-indent
This commit is contained in:
commit
5a0f18513b
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
418
VERSION.txt
418
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
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<configuration>
|
||||
<!-- never deploy to a repository, only use in reactor -->
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Class<?>> _introspectedClasses = new HashSet<>();
|
||||
private final List<IntrospectableAnnotationHandler> _handlers = new ArrayList<IntrospectableAnnotationHandler>();
|
||||
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)
|
||||
|
|
|
@ -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<Connection> 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<Connection> 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<Void> 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<Void> 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<Connection>.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<Void> 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<Connection>.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<Connection>.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<Connection>.Entry entry = (Pool<Connection>.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<Connection>.Entry entry = (Pool<Connection>.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<Connection>.Entry entry = (Pool<Connection>.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<Connection> getIdleConnections()
|
||||
{
|
||||
return pool.values().stream()
|
||||
.filter(Pool.Entry::isIdle)
|
||||
.filter(entry -> !entry.isClosed())
|
||||
.map(Pool.Entry::getPooled)
|
||||
.collect(toCollection(ArrayDeque::new));
|
||||
}
|
||||
|
||||
Collection<Connection> 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<Connection> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <code>connectionCount</code>
|
||||
* connections so they are immediately ready for use.
|
||||
* @param connectionCount the number of connections to pre-start.
|
||||
*/
|
||||
default CompletableFuture<Void> preCreateConnections(int connectionCount)
|
||||
{
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param connection the connection to test
|
||||
* @return whether the given connection is currently in use
|
||||
|
|
|
@ -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<Connection> idleConnections;
|
||||
private final Set<Connection> 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<Connection> getIdleConnections()
|
||||
{
|
||||
return idleConnections;
|
||||
}
|
||||
|
||||
public Collection<Connection> 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<Connection> 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<Connection> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Response.ResponseListener> 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(),
|
||||
|
|
|
@ -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<Connection> promise;
|
||||
private Object attachment;
|
||||
|
||||
private ProxyConnection(Destination destination, Connection connection, Promise<Connection> 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<Connection>
|
||||
|
|
|
@ -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> 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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Holder> idleConnections;
|
||||
private final Map<Connection, Holder> 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<Holder> 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<Connection> 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<Connection> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Entry> 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<Connection> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<Connection>.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<Entry> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -118,7 +118,7 @@ public class AsyncRequestContent implements Request.Content, Request.Content.Sub
|
|||
public void fail(Throwable failure)
|
||||
{
|
||||
List<Callback> 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -74,17 +74,14 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
Collection<Connection> 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<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
Collection<Connection> 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
|
||||
|
|
|
@ -89,7 +89,10 @@ public class InputStreamContentTest
|
|||
server.stop();
|
||||
}
|
||||
|
||||
private static List<BiConsumer<Request, InputStream>> content()
|
||||
/**
|
||||
* need public access to avoid jpms issue
|
||||
*/
|
||||
public static List<BiConsumer<Request, InputStream>> content()
|
||||
{
|
||||
return List.of(
|
||||
(request, stream) -> request.body(new InputStreamRequestContent(stream)),
|
||||
|
|
|
@ -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<AppProvider> _providers = new ArrayList<AppProvider>();
|
||||
private final AppLifeCycle _lifecycle = new AppLifeCycle();
|
||||
private final Queue<AppEntry> _apps = new ConcurrentLinkedQueue<AppEntry>();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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}"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure.dtd">
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Set name="contextPath">/</Set>
|
||||
</Configure>
|
||||
----
|
||||
|
||||
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}"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure.dtd">
|
||||
<Configure class="org.eclipse.jetty.overlays.TemplateContext">
|
||||
<Get name="resourceCache">
|
||||
<Set name="useFileMappedBuffer">true</Set>
|
||||
<Set name="maxCachedFileSize">10000000</Set>
|
||||
<Set name="maxCachedFiles">1000</Set>
|
||||
<Set name="maxCacheSize">64000000</Set>
|
||||
</Get>
|
||||
</Configure>
|
||||
----
|
||||
|
||||
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}"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
|
||||
version="2.4">
|
||||
<context-param>
|
||||
<param-name>jtrac.home</param-name>
|
||||
<param-value>/tmp/jtrac-${overlay.instance.classifier}</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>webAppRootKey</param-name>
|
||||
<param-value>jtrac-${overlay.instance.classifier}</param-value>
|
||||
</context-param>
|
||||
<filter>
|
||||
</web-app>
|
||||
----
|
||||
|
||||
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}"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure.dtd">
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Set name="virtualHosts">
|
||||
<Array type="String">
|
||||
<Item>127.0.0.2</Item>
|
||||
<Item>red.myVirtualDomain.com</Item>
|
||||
</Array>
|
||||
</Set>
|
||||
</Configure>
|
||||
----
|
||||
|
||||
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.
|
|
@ -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}"]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Integer> requests = new LinkedList<>();
|
||||
private final Map<Integer, HttpChannelOverFCGI> activeChannels = new ConcurrentHashMap<>();
|
||||
private final Queue<HttpChannelOverFCGI> 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<Connection> 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);
|
||||
}
|
||||
|
|
|
@ -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<Generator.Result> 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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<HttpField>
|
|||
{
|
||||
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<HttpField>
|
|||
{
|
||||
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<HttpField>
|
|||
{
|
||||
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<HttpField>
|
|||
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<HttpField>
|
|||
{
|
||||
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<HttpField>
|
|||
*/
|
||||
default List<HttpField> getFields(HttpHeader header)
|
||||
{
|
||||
return stream().filter(f -> f.getHeader().equals(header)).collect(Collectors.toList());
|
||||
return getFields(header, (f, h) -> f.getHeader() == h);
|
||||
}
|
||||
|
||||
default List<HttpField> getFields(String name)
|
||||
{
|
||||
return getFields(name, (f, n) -> f.is(name));
|
||||
}
|
||||
|
||||
private <T> List<HttpField> getFields(T header, BiPredicate<HttpField, T> predicate)
|
||||
{
|
||||
return stream()
|
||||
.filter(f -> predicate.test(f, header))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -380,7 +395,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
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<HttpField>
|
|||
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<HttpField>
|
|||
final List<String> 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<HttpField>
|
|||
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<HttpField>
|
|||
{
|
||||
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<HttpField>
|
|||
return put(name, Long.toString(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Computes a single field for the given HttpHeader and for existing fields with the same header.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
* <p>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)}.</p>
|
||||
*
|
||||
* <p>This method can be used to {@link #put(HttpField) put} a new field (or blindly replace its value):</p>
|
||||
* <pre>
|
||||
* httpFields.computeField("X-New-Header",
|
||||
* (name, fields) -> new HttpField(name, "NewValue"));
|
||||
* </pre>
|
||||
*
|
||||
* <p>This method can be used to coalesce many fields into one:</p>
|
||||
* <pre>
|
||||
* // Input:
|
||||
* GET / HTTP/1.1
|
||||
* Host: localhost
|
||||
* Cookie: foo=1
|
||||
* Cookie: bar=2,baz=3
|
||||
* User-Agent: Jetty
|
||||
*
|
||||
* // Computation:
|
||||
* httpFields.computeField("Cookie", (name, fields) ->
|
||||
* {
|
||||
* // No cookies, nothing to do.
|
||||
* if (fields == null)
|
||||
* return null;
|
||||
*
|
||||
* // Coalesces all cookies.
|
||||
* String coalesced = fields.stream()
|
||||
* .flatMap(field -> Stream.of(field.getValues()))
|
||||
* .collect(Collectors.joining(", "));
|
||||
*
|
||||
* // Returns a single Cookie header with all cookies.
|
||||
* return new HttpField(name, coalesced);
|
||||
* }
|
||||
*
|
||||
* // Output:
|
||||
* GET / HTTP/1.1
|
||||
* Host: localhost
|
||||
* Cookie: foo=1, bar=2, baz=3
|
||||
* User-Agent: Jetty
|
||||
* </pre>
|
||||
*
|
||||
* <p>This method can be used to replace a field:</p>
|
||||
* <pre>
|
||||
* httpFields.computeField("X-Length", (name, fields) ->
|
||||
* {
|
||||
* if (fields == null)
|
||||
* return null;
|
||||
*
|
||||
* // Get any value among the X-Length headers.
|
||||
* String length = fields.stream()
|
||||
* .map(HttpField::getValue)
|
||||
* .findAny()
|
||||
* .orElse("0");
|
||||
*
|
||||
* // Replace X-Length headers with X-Capacity header.
|
||||
* return new HttpField("X-Capacity", length);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* <p>This method can be used to remove a field:</p>
|
||||
* <pre>
|
||||
* httpFields.computeField("Connection", (name, fields) -> null);
|
||||
* </pre>
|
||||
*
|
||||
* @param header the HTTP header
|
||||
* @param computeFn the compute function
|
||||
*/
|
||||
public void computeField(HttpHeader header, BiFunction<HttpHeader, List<HttpField>, HttpField> computeFn)
|
||||
{
|
||||
computeField(header, computeFn, (f, h) -> f.getHeader() == h);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Computes a single field for the given HTTP header name and for existing fields with the same name.</p>
|
||||
*
|
||||
* @param name the HTTP header name
|
||||
* @param computeFn the compute function
|
||||
* @see #computeField(HttpHeader, BiFunction)
|
||||
*/
|
||||
public void computeField(String name, BiFunction<String, List<HttpField>, HttpField> computeFn)
|
||||
{
|
||||
computeField(name, computeFn, HttpField::is);
|
||||
}
|
||||
|
||||
private <T> void computeField(T header, BiFunction<T, List<HttpField>, HttpField> computeFn, BiPredicate<HttpField, T> matcher)
|
||||
{
|
||||
// Look for first occurrence
|
||||
int first = -1;
|
||||
for (int i = 0; i < _size; i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (matcher.test(f, header))
|
||||
{
|
||||
first = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the header is not found, add a new one;
|
||||
if (first < 0)
|
||||
{
|
||||
HttpField newField = computeFn.apply(header, null);
|
||||
if (newField != null)
|
||||
add(newField);
|
||||
return;
|
||||
}
|
||||
|
||||
// Are there any more occurrences?
|
||||
List<HttpField> found = null;
|
||||
for (int i = first + 1; i < _size; i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (matcher.test(f, header))
|
||||
{
|
||||
if (found == null)
|
||||
{
|
||||
found = new ArrayList<>();
|
||||
found.add(_fields[first]);
|
||||
}
|
||||
// Remember and remove additional fields
|
||||
found.add(f);
|
||||
remove(i--);
|
||||
}
|
||||
}
|
||||
|
||||
// If no additional fields were found, handle singleton case
|
||||
if (found == null)
|
||||
found = Collections.singletonList(_fields[first]);
|
||||
else
|
||||
found = Collections.unmodifiableList(found);
|
||||
|
||||
HttpField newField = computeFn.apply(header, found);
|
||||
if (newField == null)
|
||||
remove(first);
|
||||
else
|
||||
_fields[first] = newField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a field.
|
||||
*
|
||||
|
@ -927,7 +1088,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getHeader() == name)
|
||||
System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1);
|
||||
remove(i--);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -938,7 +1099,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
{
|
||||
HttpField f = _fields[i];
|
||||
if (fields.contains(f.getHeader()))
|
||||
System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1);
|
||||
remove(i--);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -954,12 +1115,19 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
for (int i = 0; i < _size; i++)
|
||||
{
|
||||
HttpField f = _fields[i];
|
||||
if (f.getName().equalsIgnoreCase(name))
|
||||
System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1);
|
||||
if (f.is(name))
|
||||
remove(i--);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void remove(int i)
|
||||
{
|
||||
_size--;
|
||||
System.arraycopy(_fields, i + 1, _fields, i, _size - i);
|
||||
_fields[_size] = null;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return _size;
|
||||
|
@ -1077,9 +1245,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
{
|
||||
if (_current < 0)
|
||||
throw new IllegalStateException();
|
||||
_size--;
|
||||
System.arraycopy(_fields, _current + 1, _fields, _current, _size - _current);
|
||||
_fields[_size] = null;
|
||||
Mutable.this.remove(_current);
|
||||
_cursor = _current;
|
||||
_current = -1;
|
||||
}
|
||||
|
@ -1141,7 +1307,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
{
|
||||
// default impl overridden for efficiency
|
||||
for (HttpField f : _fields)
|
||||
if (f.getName().equalsIgnoreCase(header))
|
||||
if (f.is(header))
|
||||
return f.getValue();
|
||||
return null;
|
||||
}
|
||||
|
@ -1171,7 +1337,7 @@ public interface HttpFields extends Iterable<HttpField>
|
|||
{
|
||||
// default impl overridden for efficiency
|
||||
for (HttpField f : _fields)
|
||||
if (f.getName().equalsIgnoreCase(name))
|
||||
if (f.is(name))
|
||||
return f;
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -291,7 +291,7 @@ public class MimeTypes
|
|||
{
|
||||
}
|
||||
|
||||
public synchronized Map<String, String> getMimeMap()
|
||||
public Map<String, String> getMimeMap()
|
||||
{
|
||||
return _mimeMap;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.ListIterator;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
|
@ -38,6 +39,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
@ -879,4 +881,45 @@ public class HttpFieldsTest
|
|||
assertThat(i.next().getName(), is("name4"));
|
||||
assertThat(i.hasNext(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStream()
|
||||
{
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
assertThat(fields.stream().count(), is(0L));
|
||||
fields.put("name1", "valueA");
|
||||
fields.put("name2", "valueB");
|
||||
fields.add("name3", "valueC");
|
||||
assertThat(fields.stream().count(), is(3L));
|
||||
assertThat(fields.stream().map(HttpField::getName).filter("name2"::equalsIgnoreCase).count(), is(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeField()
|
||||
{
|
||||
HttpFields.Mutable fields = HttpFields.build();
|
||||
assertThat(fields.size(), is(0));
|
||||
|
||||
fields.computeField("Test", (n, f) -> null);
|
||||
assertThat(fields.size(), is(0));
|
||||
|
||||
fields.add(new HttpField("Before", "value"));
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value"));
|
||||
|
||||
fields.computeField("Test", (n, f) -> new HttpField(n, "one"));
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one"));
|
||||
|
||||
fields.add(new HttpField("After", "value"));
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value"));
|
||||
|
||||
fields.add(new HttpField("Test", "two"));
|
||||
fields.add(new HttpField("Test", "three"));
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value", "Test: two", "Test: three"));
|
||||
|
||||
fields.computeField("Test", (n, f) -> new HttpField("TEST", "count=" + f.size()));
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "TEST: count=3", "After: value"));
|
||||
|
||||
fields.computeField("TEST", (n, f) -> null);
|
||||
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "After: value"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -316,7 +317,7 @@ public class RawHTTP2ProxyTest
|
|||
|
||||
private static class ClientToProxyToServer extends IteratingCallback implements Stream.Listener
|
||||
{
|
||||
private final Object lock = this;
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Map<Stream, Deque<FrameInfo>> frames = new HashMap<>();
|
||||
private final Map<Stream, Stream> streams = new HashMap<>();
|
||||
private final ServerToProxyToClient serverToProxyToClient = new ServerToProxyToClient();
|
||||
|
@ -339,7 +340,7 @@ public class RawHTTP2ProxyTest
|
|||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("CPS queueing {} for {} on {}", frame, stream, stream.getSession());
|
||||
boolean connected;
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
Deque<FrameInfo> deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>());
|
||||
deque.offer(new FrameInfo(frame, callback));
|
||||
|
@ -363,7 +364,7 @@ public class RawHTTP2ProxyTest
|
|||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("CPS connected to {} with {}", address, result);
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
proxyToServerSession = result;
|
||||
}
|
||||
|
@ -385,7 +386,7 @@ public class RawHTTP2ProxyTest
|
|||
{
|
||||
Stream proxyToServerStream = null;
|
||||
Session proxyToServerSession = null;
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
for (Map.Entry<Stream, Deque<FrameInfo>> entry : frames.entrySet())
|
||||
{
|
||||
|
@ -415,7 +416,7 @@ public class RawHTTP2ProxyTest
|
|||
@Override
|
||||
public void succeeded(Stream result)
|
||||
{
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("CPS created {}", result);
|
||||
|
@ -549,7 +550,7 @@ public class RawHTTP2ProxyTest
|
|||
|
||||
private static class ServerToProxyToClient extends IteratingCallback implements Stream.Listener
|
||||
{
|
||||
private final Object lock = this;
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Map<Stream, Deque<FrameInfo>> frames = new HashMap<>();
|
||||
private final Map<Stream, Stream> streams = new HashMap<>();
|
||||
private FrameInfo frameInfo;
|
||||
|
@ -559,7 +560,7 @@ public class RawHTTP2ProxyTest
|
|||
protected Action process() throws Throwable
|
||||
{
|
||||
Stream proxyToClientStream = null;
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
for (Map.Entry<Stream, Deque<FrameInfo>> entry : frames.entrySet())
|
||||
{
|
||||
|
@ -630,7 +631,7 @@ public class RawHTTP2ProxyTest
|
|||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SPC queueing {} for {} on {}", frame, stream, stream.getSession());
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
Deque<FrameInfo> deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>());
|
||||
deque.offer(new FrameInfo(frame, callback));
|
||||
|
@ -682,7 +683,7 @@ public class RawHTTP2ProxyTest
|
|||
|
||||
private void link(Stream proxyToServerStream, Stream clientToProxyStream)
|
||||
{
|
||||
synchronized (lock)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
streams.put(proxyToServerStream, clientToProxyStream);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.jetty.io.WriteFlusher;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
||||
import org.eclipse.jetty.util.thread.TryExecutor;
|
||||
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
|
||||
|
@ -49,6 +50,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
// TODO remove this once we are sure EWYK is OK for http2
|
||||
private static final boolean PEC_MODE = Boolean.getBoolean("org.eclipse.jetty.http2.PEC_MODE");
|
||||
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Queue<Runnable> tasks = new ArrayDeque<>();
|
||||
private final HTTP2Producer producer = new HTTP2Producer();
|
||||
private final AtomicLong bytesIn = new AtomicLong();
|
||||
|
@ -190,7 +192,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
|
||||
private void offerTask(Runnable task)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
tasks.offer(task);
|
||||
}
|
||||
|
@ -220,7 +222,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
|
|||
|
||||
private Runnable pollTask()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return tasks.poll();
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.eclipse.jetty.io.EofException;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -46,6 +47,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
private static final Logger LOG = LoggerFactory.getLogger(HTTP2Flusher.class);
|
||||
private static final ByteBuffer[] EMPTY_BYTE_BUFFERS = new ByteBuffer[0];
|
||||
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Queue<WindowEntry> windows = new ArrayDeque<>();
|
||||
private final Deque<Entry> entries = new ArrayDeque<>();
|
||||
private final Queue<Entry> pendingEntries = new ArrayDeque<>();
|
||||
|
@ -64,7 +66,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
public void window(IStream stream, WindowUpdateFrame frame)
|
||||
{
|
||||
Throwable closed;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
closed = terminated;
|
||||
if (closed == null)
|
||||
|
@ -78,7 +80,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
public boolean prepend(Entry entry)
|
||||
{
|
||||
Throwable closed;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
closed = terminated;
|
||||
if (closed == null)
|
||||
|
@ -97,7 +99,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
public boolean append(Entry entry)
|
||||
{
|
||||
Throwable closed;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
closed = terminated;
|
||||
if (closed == null)
|
||||
|
@ -115,7 +117,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
|
||||
private int getWindowQueueSize()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return windows.size();
|
||||
}
|
||||
|
@ -123,7 +125,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
|
||||
public int getFrameQueueSize()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return entries.size();
|
||||
}
|
||||
|
@ -135,7 +137,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Flushing {}", session);
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (terminated != null)
|
||||
throw terminated;
|
||||
|
@ -323,7 +325,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
|
||||
Throwable closed;
|
||||
Set<Entry> allEntries;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
closed = terminated;
|
||||
terminated = x;
|
||||
|
@ -352,7 +354,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
|||
void terminate(Throwable cause)
|
||||
{
|
||||
Throwable closed;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
closed = terminated;
|
||||
terminated = cause;
|
||||
|
|
|
@ -70,6 +70,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
|||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -1748,6 +1749,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
*/
|
||||
private class StreamCreator
|
||||
{
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Queue<Slot> slots = new ArrayDeque<>();
|
||||
private Thread flushing;
|
||||
|
||||
|
@ -1825,7 +1827,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
{
|
||||
if (streamId <= 0)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
streamId = localStreamIds.getAndAdd(2);
|
||||
slots.offer(slot);
|
||||
|
@ -1833,7 +1835,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
else
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
slots.offer(slot);
|
||||
}
|
||||
|
@ -1843,7 +1845,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
|
||||
private void releaseSlotFlushAndFail(Slot slot, Promise<Stream> promise, Throwable x)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
slots.remove(slot);
|
||||
}
|
||||
|
@ -1870,7 +1872,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
while (true)
|
||||
{
|
||||
ControlEntry entry;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (flushing == null)
|
||||
flushing = thread;
|
||||
|
|
|
@ -143,7 +143,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
@Override
|
||||
public void reset(ResetFrame frame, Callback callback)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (isReset())
|
||||
return;
|
||||
|
@ -156,7 +156,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
private boolean startWrite(Callback callback)
|
||||
{
|
||||
Throwable failure;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
failure = this.failure;
|
||||
if (failure == null && sendCallback == null)
|
||||
|
@ -192,7 +192,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
@Override
|
||||
public boolean isReset()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return localReset || remoteReset;
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
|
||||
private boolean isFailed()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return failure != null;
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
@Override
|
||||
public boolean isResetOrFailed()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return isReset() || isFailed();
|
||||
}
|
||||
|
@ -476,7 +476,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
|
||||
private void onReset(ResetFrame frame, Callback callback)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
remoteReset = true;
|
||||
failure = new EofException("reset");
|
||||
|
@ -501,7 +501,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
|
||||
private void onFailure(FailureFrame frame, Callback callback)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
failure = frame.getFailure();
|
||||
}
|
||||
|
@ -689,7 +689,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
|
||||
private Callback endWrite()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
Callback callback = sendCallback;
|
||||
sendCallback = null;
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Invocable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -43,6 +44,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class);
|
||||
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Deque<Entry> dataQueue = new ArrayDeque<>();
|
||||
private final AtomicReference<WriteState> writeState = new AtomicReference<>(WriteState.IDLE);
|
||||
private final AtomicReference<Callback> readCallback = new AtomicReference<>();
|
||||
|
@ -172,7 +174,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
|
|||
public int fill(ByteBuffer sink) throws IOException
|
||||
{
|
||||
Entry entry;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
entry = dataQueue.poll();
|
||||
}
|
||||
|
@ -206,7 +208,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
|
|||
|
||||
if (source.hasRemaining())
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
dataQueue.offerFirst(entry);
|
||||
}
|
||||
|
@ -548,7 +550,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
|
|||
|
||||
private void offer(ByteBuffer buffer, Callback callback, Throwable failure)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
dataQueue.offer(new Entry(buffer, callback, failure));
|
||||
}
|
||||
|
@ -557,7 +559,7 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
|
|||
protected void process()
|
||||
{
|
||||
boolean empty;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
empty = dataQueue.isEmpty();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.Closeable;
|
|||
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.Frame;
|
||||
import org.eclipse.jetty.util.Attachable;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
|
@ -29,21 +30,8 @@ import org.eclipse.jetty.util.Callback;
|
|||
* <p>This class extends {@link Stream} by adding the methods required to
|
||||
* implement the HTTP/2 stream functionalities.</p>
|
||||
*/
|
||||
public interface IStream extends Stream, Closeable
|
||||
public interface IStream extends Stream, Attachable, Closeable
|
||||
{
|
||||
/**
|
||||
* @return the object attached to this stream
|
||||
* @see #setAttachment(Object)
|
||||
*/
|
||||
public Object getAttachment();
|
||||
|
||||
/**
|
||||
* Attaches the given object to this stream for later retrieval.
|
||||
*
|
||||
* @param attachment the object to attach to this stream
|
||||
*/
|
||||
public void setAttachment(Object attachment);
|
||||
|
||||
/**
|
||||
* @return whether this stream is local or remote
|
||||
*/
|
||||
|
|
|
@ -34,9 +34,13 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
|
|||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HttpChannelOverHTTP2 extends HttpChannel
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHTTP2.class);
|
||||
|
||||
private final Stream.Listener listener = new Listener();
|
||||
private final HttpConnectionOverHTTP2 connection;
|
||||
private final Session session;
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.eclipse.jetty.http2.frames.ResetFrame;
|
|||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -246,6 +247,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
|
||||
private class ContentNotifier
|
||||
{
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Queue<DataInfo> queue = new ArrayDeque<>();
|
||||
private final HttpReceiverOverHTTP2 receiver;
|
||||
private DataInfo dataInfo;
|
||||
|
@ -269,7 +271,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
|
||||
private void enqueue(DataInfo dataInfo)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
queue.offer(dataInfo);
|
||||
}
|
||||
|
@ -285,7 +287,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
return;
|
||||
|
||||
// Process only if there is demand.
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (!resume && demand() <= 0)
|
||||
{
|
||||
|
@ -309,7 +311,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
}
|
||||
}
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
dataInfo = queue.poll();
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -347,7 +349,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
|
||||
private boolean active(boolean resume)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
|
@ -380,7 +382,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
*/
|
||||
private boolean stall()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
if (resume)
|
||||
{
|
||||
|
@ -400,7 +402,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
private void reset()
|
||||
{
|
||||
dataInfo = null;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
queue.clear();
|
||||
active = false;
|
||||
|
|
|
@ -58,6 +58,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
|
||||
public class HTTP2ServerConnection extends HTTP2Connection
|
||||
{
|
||||
|
@ -84,7 +85,8 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final Queue<HttpChannelOverHTTP2> channels = new ArrayDeque<>();
|
||||
private final List<Frame> upgradeFrames = new ArrayList<>();
|
||||
private final AtomicLong totalRequests = new AtomicLong();
|
||||
|
@ -287,7 +289,7 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
|||
{
|
||||
if (isRecycleHttpChannels())
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
channels.offer(channel);
|
||||
}
|
||||
|
@ -298,7 +300,7 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
|||
{
|
||||
if (isRecycleHttpChannels())
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
return channels.poll();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -413,6 +414,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
*/
|
||||
private class TransportCallback implements Callback
|
||||
{
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private State _state = State.IDLE;
|
||||
private Callback _callback;
|
||||
private boolean _commit;
|
||||
|
@ -420,7 +422,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
|
||||
private void reset(Throwable failure)
|
||||
{
|
||||
assert Thread.holdsLock(this);
|
||||
assert _lock.isHeldByCurrentThread();
|
||||
_state = failure != null ? State.FAILED : State.IDLE;
|
||||
_callback = null;
|
||||
_commit = false;
|
||||
|
@ -443,7 +445,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
|
||||
private Throwable sending(Callback callback, boolean commit)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -471,7 +473,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
{
|
||||
Callback callback;
|
||||
boolean commit;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_state != State.SENDING)
|
||||
{
|
||||
|
@ -495,7 +497,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
{
|
||||
Callback callback;
|
||||
boolean commit;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_state != State.SENDING)
|
||||
{
|
||||
|
@ -517,7 +519,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
private boolean idleTimeout(Throwable failure)
|
||||
{
|
||||
Callback callback = null;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
// Ignore idle timeouts if not writing,
|
||||
// as the application may be suspended.
|
||||
|
@ -542,7 +544,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
public InvocationType getInvocationType()
|
||||
{
|
||||
Callback callback;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
callback = _callback;
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
|||
public void doShutdownOutput()
|
||||
{
|
||||
super.doShutdownOutput();
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_hasOutput.signalAll();
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
|||
public void doClose()
|
||||
{
|
||||
super.doClose();
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_hasOutput.signalAll();
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
|||
{
|
||||
ByteBuffer b;
|
||||
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
while (BufferUtil.isEmpty(_out) && !isOutputShutdown())
|
||||
{
|
||||
|
@ -401,7 +401,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
|||
public boolean flush(ByteBuffer... buffers) throws IOException
|
||||
{
|
||||
boolean flushed = true;
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (!isOpen())
|
||||
throw new IOException("CLOSED");
|
||||
|
@ -447,7 +447,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
|
|||
@Override
|
||||
public void reset()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_inQ.clear();
|
||||
_hasOutput.signalAll();
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.io;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jetty.util.IncludeExcludeSet;
|
||||
|
||||
public class IncludeExcludeConnectionStatistics extends ConnectionStatistics
|
||||
{
|
||||
private final IncludeExcludeSet<Class<? extends Connection>, Connection> _set = new IncludeExcludeSet<>(ConnectionSet.class);
|
||||
|
||||
public void include(String className) throws ClassNotFoundException
|
||||
{
|
||||
_set.include(connectionForName(className));
|
||||
}
|
||||
|
||||
public void include(Class<? extends Connection> clazz)
|
||||
{
|
||||
_set.include(clazz);
|
||||
}
|
||||
|
||||
public void exclude(String className) throws ClassNotFoundException
|
||||
{
|
||||
_set.exclude(connectionForName(className));
|
||||
}
|
||||
|
||||
public void exclude(Class<? extends Connection> clazz)
|
||||
{
|
||||
_set.exclude(clazz);
|
||||
}
|
||||
|
||||
private Class<? extends Connection> connectionForName(String className) throws ClassNotFoundException
|
||||
{
|
||||
Class<?> aClass = Class.forName(className);
|
||||
if (!Connection.class.isAssignableFrom(aClass))
|
||||
throw new IllegalArgumentException("Class is not a Connection");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Connection> connectionClass = (Class<? extends Connection>)aClass;
|
||||
return connectionClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened(Connection connection)
|
||||
{
|
||||
if (_set.test(connection))
|
||||
super.onOpened(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(Connection connection)
|
||||
{
|
||||
if (_set.test(connection))
|
||||
super.onClosed(connection);
|
||||
}
|
||||
|
||||
public static class ConnectionSet extends AbstractSet<Class<? extends Connection>> implements Predicate<Connection>
|
||||
{
|
||||
private final Set<Class<? extends Connection>> set = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public boolean add(Class<? extends Connection> aClass)
|
||||
{
|
||||
return set.add(aClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o)
|
||||
{
|
||||
return set.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Class<? extends Connection>> iterator()
|
||||
{
|
||||
return set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Connection connection)
|
||||
{
|
||||
if (connection == null)
|
||||
return false;
|
||||
return set.stream().anyMatch(c -> c.isAssignableFrom(connection.getClass()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ import org.eclipse.jetty.util.IO;
|
|||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
|
||||
|
@ -79,6 +80,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
}
|
||||
}
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final AtomicBoolean _started = new AtomicBoolean(false);
|
||||
private boolean _selecting;
|
||||
private final SelectorManager _selectorManager;
|
||||
|
@ -252,7 +254,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
LOG.debug("Queued change lazy={} {} on {}", lazy, update, this);
|
||||
|
||||
Selector selector = null;
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_updates.offer(update);
|
||||
|
||||
|
@ -278,7 +280,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
LOG.debug("Wakeup {}", this);
|
||||
|
||||
Selector selector = null;
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_selecting)
|
||||
{
|
||||
|
@ -382,7 +384,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
|
||||
private int getActionSize()
|
||||
{
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _updates.size();
|
||||
}
|
||||
|
@ -424,7 +426,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
{
|
||||
DumpKeys dump = new DumpKeys();
|
||||
String updatesAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
updates = new ArrayList<>(_updates);
|
||||
_updates.addFirst(dump);
|
||||
|
@ -514,7 +516,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
|
||||
private void processUpdates()
|
||||
{
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
Deque<SelectorUpdate> updates = _updates;
|
||||
_updates = _updateable;
|
||||
|
@ -543,7 +545,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
|
||||
Selector selector;
|
||||
int updates;
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
updates = _updates.size();
|
||||
_selecting = updates == 0;
|
||||
|
@ -579,7 +581,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size());
|
||||
|
||||
int updates;
|
||||
synchronized (ManagedSelector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
// finished selecting
|
||||
_selecting = false;
|
||||
|
@ -887,7 +889,11 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
{
|
||||
this.channel = channel;
|
||||
this.attachment = attachment;
|
||||
this.timeout = ManagedSelector.this._selectorManager.getScheduler().schedule(this, ManagedSelector.this._selectorManager.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
||||
long timeout = ManagedSelector.this._selectorManager.getConnectTimeout();
|
||||
if (timeout > 0)
|
||||
this.timeout = ManagedSelector.this._selectorManager.getScheduler().schedule(this, timeout, TimeUnit.MILLISECONDS);
|
||||
else
|
||||
this.timeout = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -918,7 +924,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
{
|
||||
if (failed.compareAndSet(false, true))
|
||||
{
|
||||
timeout.cancel();
|
||||
if (timeout != null)
|
||||
timeout.cancel();
|
||||
IO.close(channel);
|
||||
ManagedSelector.this._selectorManager.connectionFailed(channel, failure, attachment);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.nio.channels.Selector;
|
|||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Invocable;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -42,6 +43,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SocketChannelEndPoint.class);
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final SocketChannel _channel;
|
||||
private final ManagedSelector _selector;
|
||||
private SelectionKey _key;
|
||||
|
@ -317,7 +319,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
|
|||
int readyOps = _key.readyOps();
|
||||
int oldInterestOps;
|
||||
int newInterestOps;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_updatePending = true;
|
||||
// Remove the readyOps, that here can only be OP_READ or OP_WRITE (or both).
|
||||
|
@ -361,7 +363,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
|
|||
{
|
||||
int oldInterestOps;
|
||||
int newInterestOps;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_updatePending = false;
|
||||
oldInterestOps = _currentInterestOps;
|
||||
|
@ -403,7 +405,7 @@ public class SocketChannelEndPoint extends AbstractEndPoint implements ManagedSe
|
|||
int oldInterestOps;
|
||||
int newInterestOps;
|
||||
boolean pending;
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
pending = _updatePending;
|
||||
oldInterestOps = _desiredInterestOps;
|
||||
|
|
|
@ -33,7 +33,6 @@ public class WriterOutputStream extends OutputStream
|
|||
{
|
||||
protected final Writer _writer;
|
||||
protected final Charset _encoding;
|
||||
private final byte[] _buf = new byte[1];
|
||||
|
||||
public WriterOutputStream(Writer writer, String encoding)
|
||||
{
|
||||
|
@ -82,11 +81,9 @@ public class WriterOutputStream extends OutputStream
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int b)
|
||||
public void write(int b)
|
||||
throws IOException
|
||||
{
|
||||
_buf[0] = (byte)b;
|
||||
write(_buf);
|
||||
write(new byte[]{(byte)b});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.eclipse.jetty.io.WriteFlusher;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Invocable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -104,6 +105,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
WAIT_FOR_FILL // Waiting for a fill to happen
|
||||
}
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final List<SslHandshakeListener> handshakeListeners = new ArrayList<>();
|
||||
private final ByteBufferPool _bufferPool;
|
||||
private final SSLEngine _sslEngine;
|
||||
|
@ -435,7 +437,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
|
||||
private void releaseEncryptedOutputBuffer()
|
||||
{
|
||||
if (!Thread.holdsLock(_decryptedEndPoint))
|
||||
if (!_lock.isHeldByCurrentThread())
|
||||
throw new IllegalStateException();
|
||||
if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
|
||||
{
|
||||
|
@ -514,7 +516,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
|
||||
boolean waitingForFill;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onFillable {}", SslConnection.this);
|
||||
|
@ -527,7 +529,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
|
||||
if (waitingForFill)
|
||||
{
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
waitingForFill = _flushState == FlushState.WAIT_FOR_FILL;
|
||||
}
|
||||
|
@ -545,7 +547,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
|
||||
boolean fail = false;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onFillableFail {}", SslConnection.this, failure);
|
||||
|
@ -594,7 +596,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
try
|
||||
{
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(">fill {}", SslConnection.this);
|
||||
|
@ -814,7 +816,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
boolean fillable;
|
||||
ByteBuffer write = null;
|
||||
boolean interest = false;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(">needFillInterest s={}/{} uf={} ei={} di={} {}",
|
||||
|
@ -958,7 +960,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
try
|
||||
{
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
|
@ -1148,7 +1150,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
boolean fillInterest = false;
|
||||
ByteBuffer write = null;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(">onIncompleteFlush {} {}", SslConnection.this, BufferUtil.toDetailString(_encryptedOutput));
|
||||
|
@ -1242,7 +1244,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
boolean close;
|
||||
boolean flush = false;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
boolean ishut = endPoint.isInputShutdown();
|
||||
boolean oshut = endPoint.isOutputShutdown();
|
||||
|
@ -1268,7 +1270,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
// If we still can't flush, but we are not closing the endpoint,
|
||||
// let's just flush the encrypted output in the background.
|
||||
ByteBuffer write = null;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (BufferUtil.hasContent(_encryptedOutput))
|
||||
{
|
||||
|
@ -1280,7 +1282,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
endPoint.write(Callback.from(() ->
|
||||
{
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_flushState = FlushState.IDLE;
|
||||
releaseEncryptedOutputBuffer();
|
||||
|
@ -1455,7 +1457,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
|
||||
private Throwable handleException(Throwable x, String context)
|
||||
{
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_failure == null)
|
||||
{
|
||||
|
@ -1497,7 +1499,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
boolean fillable;
|
||||
boolean interested;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("IncompleteWriteCB succeeded {}", SslConnection.this);
|
||||
|
@ -1522,7 +1524,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
public void failed(final Throwable x)
|
||||
{
|
||||
boolean failFillInterest;
|
||||
synchronized (_decryptedEndPoint)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("IncompleteWriteCB failed {}", SslConnection.this, x);
|
||||
|
|
|
@ -214,7 +214,7 @@ public class SslConnectionTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onFillable()
|
||||
public void onFillable()
|
||||
{
|
||||
EndPoint endp = getEndPoint();
|
||||
try
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
|
||||
/**
|
||||
* UserInfo
|
||||
|
@ -34,7 +35,7 @@ import org.eclipse.jetty.util.security.Credential;
|
|||
*/
|
||||
public class UserInfo
|
||||
{
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private String _userName;
|
||||
private Credential _credential;
|
||||
protected List<String> _roleNames = new ArrayList<>();
|
||||
|
@ -80,7 +81,7 @@ public class UserInfo
|
|||
|
||||
public void fetchRoles() throws Exception
|
||||
{
|
||||
synchronized (_roleNames)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (!_rolesLoaded)
|
||||
{
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.eclipse.jetty.jndi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
@ -32,6 +31,7 @@ import javax.naming.spi.ObjectFactory;
|
|||
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -63,7 +63,7 @@ public class ContextFactory implements ObjectFactory
|
|||
/**
|
||||
* Map of classloaders to contexts.
|
||||
*/
|
||||
private static final Map<ClassLoader, Context> __contextMap = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private static final Map<ClassLoader, Context> __contextMap = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Threadlocal for injecting a context to use
|
||||
|
@ -77,6 +77,8 @@ public class ContextFactory implements ObjectFactory
|
|||
*/
|
||||
private static final ThreadLocal<ClassLoader> __threadClassLoader = new ThreadLocal<ClassLoader>();
|
||||
|
||||
private static final AutoLock __lock = new AutoLock();
|
||||
|
||||
/**
|
||||
* Find or create a context which pertains to a classloader.
|
||||
* <p>
|
||||
|
@ -116,7 +118,7 @@ public class ContextFactory implements ObjectFactory
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Using threadlocal classloader");
|
||||
synchronized (__contextMap)
|
||||
try (AutoLock l = __lock.lock())
|
||||
{
|
||||
ctx = getContextForClassLoader(loader);
|
||||
if (ctx == null)
|
||||
|
@ -137,7 +139,7 @@ public class ContextFactory implements ObjectFactory
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Trying thread context classloader");
|
||||
synchronized (__contextMap)
|
||||
try (AutoLock l = __lock.lock())
|
||||
{
|
||||
while (ctx == null && loader != null)
|
||||
{
|
||||
|
@ -163,7 +165,7 @@ public class ContextFactory implements ObjectFactory
|
|||
{
|
||||
if (LOG.isDebugEnabled() && loader != null)
|
||||
LOG.debug("Trying classloader of current org.eclipse.jetty.server.handler.ContextHandler");
|
||||
synchronized (__contextMap)
|
||||
try (AutoLock l = __lock.lock())
|
||||
{
|
||||
loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader();
|
||||
ctx = (Context)__contextMap.get(loader);
|
||||
|
@ -220,8 +222,10 @@ public class ContextFactory implements ObjectFactory
|
|||
{
|
||||
if (loader == null)
|
||||
return null;
|
||||
|
||||
return (Context)__contextMap.get(loader);
|
||||
try (AutoLock l = __lock.lock())
|
||||
{
|
||||
return __contextMap.get(loader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,7 +261,7 @@ public class ContextFactory implements ObjectFactory
|
|||
|
||||
public static void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
synchronized (__contextMap)
|
||||
try (AutoLock l = __lock.lock())
|
||||
{
|
||||
Dumpable.dumpObjects(out, indent, String.format("o.e.j.jndi.ContextFactory@%x", __contextMap.hashCode()), __contextMap);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<version>2.13.2</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -278,7 +278,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
|
|||
}
|
||||
|
||||
// Attempt to login with the provided authCode
|
||||
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration);
|
||||
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request));
|
||||
UserIdentity user = login(null, credentials, request);
|
||||
if (user != null)
|
||||
{
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.util.Arrays;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.FormRequestContent;
|
||||
|
@ -38,7 +37,7 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
* <p>
|
||||
* This is constructed with an authorization code from the authentication request. This authorization code
|
||||
* is then exchanged using {@link #redeemAuthCode(HttpClient)} for a response containing the ID Token and Access Token.
|
||||
* is then exchanged using {@link #redeemAuthCode(OpenIdConfiguration)} for a response containing the ID Token and Access Token.
|
||||
* The response is then validated against the {@link OpenIdConfiguration}.
|
||||
* </p>
|
||||
*/
|
||||
|
@ -48,16 +47,14 @@ public class OpenIdCredentials implements Serializable
|
|||
private static final long serialVersionUID = 4766053233370044796L;
|
||||
|
||||
private final String redirectUri;
|
||||
private final OpenIdConfiguration configuration;
|
||||
private String authCode;
|
||||
private Map<String, Object> response;
|
||||
private Map<String, Object> claims;
|
||||
|
||||
public OpenIdCredentials(String authCode, String redirectUri, OpenIdConfiguration configuration)
|
||||
public OpenIdCredentials(String authCode, String redirectUri)
|
||||
{
|
||||
this.authCode = authCode;
|
||||
this.redirectUri = redirectUri;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getUserId()
|
||||
|
@ -75,7 +72,7 @@ public class OpenIdCredentials implements Serializable
|
|||
return response;
|
||||
}
|
||||
|
||||
public void redeemAuthCode(HttpClient httpClient) throws Exception
|
||||
public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("redeemAuthCode() {}", this);
|
||||
|
@ -84,26 +81,26 @@ public class OpenIdCredentials implements Serializable
|
|||
{
|
||||
try
|
||||
{
|
||||
response = claimAuthCode(httpClient, authCode);
|
||||
response = claimAuthCode(configuration);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("response: {}", response);
|
||||
|
||||
String idToken = (String)response.get("id_token");
|
||||
if (idToken == null)
|
||||
throw new IllegalArgumentException("no id_token");
|
||||
throw new AuthenticationException("no id_token");
|
||||
|
||||
String accessToken = (String)response.get("access_token");
|
||||
if (accessToken == null)
|
||||
throw new IllegalArgumentException("no access_token");
|
||||
throw new AuthenticationException("no access_token");
|
||||
|
||||
String tokenType = (String)response.get("token_type");
|
||||
if (!"Bearer".equalsIgnoreCase(tokenType))
|
||||
throw new IllegalArgumentException("invalid token_type");
|
||||
throw new AuthenticationException("invalid token_type");
|
||||
|
||||
claims = JwtDecoder.decode(idToken);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("claims {}", claims);
|
||||
validateClaims();
|
||||
validateClaims(configuration);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -113,22 +110,28 @@ public class OpenIdCredentials implements Serializable
|
|||
}
|
||||
}
|
||||
|
||||
private void validateClaims()
|
||||
private void validateClaims(OpenIdConfiguration configuration) throws Exception
|
||||
{
|
||||
// Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim.
|
||||
if (!configuration.getIssuer().equals(claims.get("iss")))
|
||||
throw new IllegalArgumentException("Issuer Identifier MUST exactly match the iss Claim");
|
||||
throw new AuthenticationException("Issuer Identifier MUST exactly match the iss Claim");
|
||||
|
||||
// The aud (audience) Claim MUST contain the client_id value.
|
||||
validateAudience();
|
||||
validateAudience(configuration);
|
||||
|
||||
// If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value.
|
||||
Object azp = claims.get("azp");
|
||||
if (azp != null && !configuration.getClientId().equals(azp))
|
||||
throw new IllegalArgumentException("Authorized party claim value should be the client_id");
|
||||
throw new AuthenticationException("Authorized party claim value should be the client_id");
|
||||
|
||||
// Check that the ID token has not expired by checking the exp claim.
|
||||
long expiry = (Long)claims.get("exp");
|
||||
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
|
||||
if (currentTimeSeconds > expiry)
|
||||
throw new AuthenticationException("ID Token has expired");
|
||||
}
|
||||
|
||||
private void validateAudience()
|
||||
private void validateAudience(OpenIdConfiguration configuration) throws AuthenticationException
|
||||
{
|
||||
Object aud = claims.get("aud");
|
||||
String clientId = configuration.getClientId();
|
||||
|
@ -137,38 +140,21 @@ public class OpenIdCredentials implements Serializable
|
|||
boolean isValidType = isString || isList;
|
||||
|
||||
if (isString && !clientId.equals(aud))
|
||||
throw new IllegalArgumentException("Audience Claim MUST contain the client_id value");
|
||||
throw new AuthenticationException("Audience Claim MUST contain the client_id value");
|
||||
else if (isList)
|
||||
{
|
||||
if (!Arrays.asList((Object[])aud).contains(clientId))
|
||||
throw new IllegalArgumentException("Audience Claim MUST contain the client_id value");
|
||||
throw new AuthenticationException("Audience Claim MUST contain the client_id value");
|
||||
|
||||
if (claims.get("azp") == null)
|
||||
throw new IllegalArgumentException("A multi-audience ID token needs to contain an azp claim");
|
||||
throw new AuthenticationException("A multi-audience ID token needs to contain an azp claim");
|
||||
}
|
||||
else if (!isValidType)
|
||||
throw new IllegalArgumentException("Audience claim was not valid");
|
||||
throw new AuthenticationException("Audience claim was not valid");
|
||||
}
|
||||
|
||||
public boolean isExpired()
|
||||
{
|
||||
if (authCode != null || claims == null)
|
||||
return true;
|
||||
|
||||
// Check expiry
|
||||
long expiry = (Long)claims.get("exp");
|
||||
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
|
||||
if (currentTimeSeconds > expiry)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("OpenId Credentials expired {}", this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Map<String, Object> claimAuthCode(HttpClient httpClient, String authCode) throws Exception
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> claimAuthCode(OpenIdConfiguration configuration) throws Exception
|
||||
{
|
||||
Fields fields = new Fields();
|
||||
fields.add("code", authCode);
|
||||
|
@ -177,7 +163,7 @@ public class OpenIdCredentials implements Serializable
|
|||
fields.add("redirect_uri", redirectUri);
|
||||
fields.add("grant_type", "authorization_code");
|
||||
FormRequestContent formContent = new FormRequestContent(fields);
|
||||
Request request = httpClient.POST(configuration.getTokenEndpoint())
|
||||
Request request = configuration.getHttpClient().POST(configuration.getTokenEndpoint())
|
||||
.body(formContent)
|
||||
.timeout(10, TimeUnit.SECONDS);
|
||||
ContentResponse response = request.send();
|
||||
|
@ -187,7 +173,15 @@ public class OpenIdCredentials implements Serializable
|
|||
|
||||
Object parsedResponse = new JSON().fromJSON(responseBody);
|
||||
if (!(parsedResponse instanceof Map))
|
||||
throw new IllegalStateException("Malformed response from OpenID Provider");
|
||||
throw new AuthenticationException("Malformed response from OpenID Provider");
|
||||
return (Map<String, Object>)parsedResponse;
|
||||
}
|
||||
|
||||
public static class AuthenticationException extends Exception
|
||||
{
|
||||
public AuthenticationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,9 @@
|
|||
|
||||
package org.eclipse.jetty.security.openid;
|
||||
|
||||
import java.security.Principal;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
|
@ -43,7 +41,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
|
|||
|
||||
private final OpenIdConfiguration configuration;
|
||||
private final LoginService loginService;
|
||||
private final HttpClient httpClient;
|
||||
private IdentityService identityService;
|
||||
private boolean authenticateNewUsers;
|
||||
|
||||
|
@ -63,7 +60,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
|
|||
{
|
||||
this.configuration = configuration;
|
||||
this.loginService = loginService;
|
||||
this.httpClient = configuration.getHttpClient();
|
||||
addBean(this.configuration);
|
||||
addBean(this.loginService);
|
||||
}
|
||||
|
@ -88,9 +84,7 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
|
|||
OpenIdCredentials openIdCredentials = (OpenIdCredentials)credentials;
|
||||
try
|
||||
{
|
||||
openIdCredentials.redeemAuthCode(httpClient);
|
||||
if (openIdCredentials.isExpired())
|
||||
return null;
|
||||
openIdCredentials.redeemAuthCode(configuration);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
|
@ -141,12 +135,10 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
|
|||
@Override
|
||||
public boolean validate(UserIdentity user)
|
||||
{
|
||||
Principal userPrincipal = user.getUserPrincipal();
|
||||
if (!(userPrincipal instanceof OpenIdUserPrincipal))
|
||||
if (!(user.getUserPrincipal() instanceof OpenIdUserPrincipal))
|
||||
return false;
|
||||
|
||||
OpenIdCredentials credentials = ((OpenIdUserPrincipal)userPrincipal).getCredentials();
|
||||
return !credentials.isExpired();
|
||||
return loginService == null || loginService.validate(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -170,5 +162,7 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
|
|||
@Override
|
||||
public void logout(UserIdentity user)
|
||||
{
|
||||
if (loginService != null)
|
||||
loginService.logout(user);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,9 +129,11 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
|
|||
public Enumeration<URL> getResources(String name) throws IOException
|
||||
{
|
||||
Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
|
||||
if (osgiUrls != null && osgiUrls.hasMoreElements())
|
||||
return osgiUrls;
|
||||
|
||||
Enumeration<URL> urls = super.getResources(name);
|
||||
List<URL> resources = toList(osgiUrls, urls);
|
||||
return Collections.enumeration(resources);
|
||||
return urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -96,37 +96,40 @@ public class OSGiClassLoader extends URLClassLoader
|
|||
}
|
||||
|
||||
@Override
|
||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
|
||||
{
|
||||
Class<?> c = findLoadedClass(name);
|
||||
ClassNotFoundException ex = null;
|
||||
boolean triedParent = false;
|
||||
|
||||
if (c == null)
|
||||
synchronized (getClassLoadingLock(name))
|
||||
{
|
||||
try
|
||||
Class<?> c = findLoadedClass(name);
|
||||
ClassNotFoundException ex = null;
|
||||
boolean triedParent = false;
|
||||
|
||||
if (c == null)
|
||||
{
|
||||
c = this.findClass(name);
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
ex = e;
|
||||
try
|
||||
{
|
||||
c = this.findClass(name);
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
ex = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == null && _parent != null && !triedParent)
|
||||
c = _parent.loadClass(name);
|
||||
|
||||
if (c == null)
|
||||
throw ex;
|
||||
|
||||
if (resolve)
|
||||
resolveClass(c);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("loaded " + c + " from " + c.getClassLoader());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
if (c == null && _parent != null && !triedParent)
|
||||
c = _parent.loadClass(name);
|
||||
|
||||
if (c == null)
|
||||
throw ex;
|
||||
|
||||
if (resolve)
|
||||
resolveClass(c);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("loaded " + c + " from " + c.getClassLoader());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<module>jetty-osgi-boot-warurl</module>
|
||||
<module>jetty-osgi-httpservice</module>
|
||||
<module>test-jetty-osgi-webapp</module>
|
||||
<module>test-jetty-osgi-webapp-resources</module>
|
||||
<module>test-jetty-osgi-context</module>
|
||||
<module>test-jetty-osgi-fragment</module>
|
||||
<module>test-jetty-osgi-server</module>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||
<artifactId>jetty-osgi-project</artifactId>
|
||||
<version>10.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>test-jetty-osgi-webapp-resources</artifactId>
|
||||
<name>OSGi Test :: Webapp With Resources</name>
|
||||
<url>http://www.eclipse.org/jetty</url>
|
||||
<packaging>war</packaging>
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.webapp.resources</bundle-symbolic-name>
|
||||
</properties>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- No point building javadoc on testing projects -->
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/target/classes</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<supportedProjectTypes>
|
||||
<supportedProjectType>war</supportedProjectType>
|
||||
</supportedProjectTypes>
|
||||
<instructions>
|
||||
<Export-Package>!com.acme*</Export-Package>
|
||||
<Web-ContextPath>/</Web-ContextPath>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- also make this webapp an osgi bundle -->
|
||||
<plugin>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- must deploy: required for jetty-distribution -->
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package com.acme;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Dump Servlet Request.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class HelloWorld extends HttpServlet
|
||||
{
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException
|
||||
{
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
doGet(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
response.setContentType("text/html");
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
out.println("<html>");
|
||||
out.println("<h1>Hello World</h1>");
|
||||
|
||||
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("fake.properties");
|
||||
|
||||
while (resources.hasMoreElements())
|
||||
out.println(resources.nextElement().toString());
|
||||
|
||||
out.println("</html>");
|
||||
out.flush();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
metadata-complete="false"
|
||||
version="3.1">
|
||||
|
||||
<display-name>WebApp With Resources</display-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Hello</servlet-name>
|
||||
<servlet-class>com.acme.HelloWorld</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Hello</servlet-name>
|
||||
<url-pattern>/hello/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
|
||||
|
|
@ -38,6 +38,12 @@
|
|||
<artifactId>pax-exam-container-forked</artifactId>
|
||||
<version>${pax.exam.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bndlib</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.swissbox</groupId>
|
||||
|
@ -69,17 +75,6 @@
|
|||
<version>${pax.url.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.tinybundles</groupId>
|
||||
<artifactId>tinybundles</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.url</groupId>
|
||||
<artifactId>pax-url-wrap</artifactId>
|
||||
|
@ -95,7 +90,7 @@
|
|||
<dependency>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>biz.aQute.bndlib</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.1.2</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
|
@ -393,6 +388,13 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||
<artifactId>test-jetty-osgi-webapp-resources</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>war</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||
<artifactId>test-jetty-osgi-fragment</artifactId>
|
||||
|
@ -550,6 +552,23 @@
|
|||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>process-test-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includeArtifactIds>test-jetty-osgi-webapp-resources</includeArtifactIds>
|
||||
<outputDirectory>target</outputDirectory>
|
||||
<stripVersion>true</stripVersion>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.servicemix.tooling</groupId>
|
||||
<artifactId>depends-maven-plugin</artifactId>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<!-- ============================================================= --><!-- Configure the Jetty Server instance with an ID "Server" --><!-- by adding an HTTP connector. --><!-- This configuration must be used in conjunction with jetty.xml --><!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
<!-- -->
|
||||
<!-- Consult the javadoc of o.e.j.server.ServerConnector and -->
|
||||
<!-- o.e.j.server.HttpConnectionFactory for all configuration -->
|
||||
<!-- that may be set here. -->
|
||||
<!-- =========================================================== -->
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg name="server"><Ref refid="Server" /></Arg>
|
||||
<Arg name="factories">
|
||||
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||
<Arg name="config"><Ref refid="httpConfig" /></Arg>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Arg>
|
||||
<Call name="addEventListener">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.osgi.boot.utils.ServerConnectorListener">
|
||||
<Set name="sysPropertyName">boot.resources.port</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<Set name="host"><Property name="jetty.http.host" /></Set>
|
||||
<Set name="port"><Property name="jetty.http.port" default="80" /></Set>
|
||||
<Set name="idleTimeout"><Property name="jetty.http.idleTimeout" default="30000"/></Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
</Configure>
|
|
@ -92,7 +92,6 @@ public class TestJettyOSGiBootWithJavaxWebSocket
|
|||
{
|
||||
List<Option> res = new ArrayList<>();
|
||||
res.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("biz.aQute.bndlib").versionAsInProject().start());
|
||||
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").versionAsInProject().start());
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.osgi.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import aQute.bnd.osgi.Constants;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.ops4j.pax.exam.Configuration;
|
||||
import org.ops4j.pax.exam.CoreOptions;
|
||||
import org.ops4j.pax.exam.Option;
|
||||
import org.ops4j.pax.exam.junit.PaxExam;
|
||||
import org.ops4j.pax.tinybundles.core.TinyBundle;
|
||||
import org.ops4j.pax.tinybundles.core.TinyBundles;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
|
||||
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
|
||||
|
||||
/**
|
||||
* TestJettyOSGiClasspathResources
|
||||
*
|
||||
*/
|
||||
|
||||
@RunWith(PaxExam.class)
|
||||
public class TestJettyOSGiClasspathResources
|
||||
{
|
||||
@Inject
|
||||
BundleContext bundleContext = null;
|
||||
|
||||
@Configuration
|
||||
public static Option[] configure()
|
||||
{
|
||||
ArrayList<Option> options = new ArrayList<>();
|
||||
options.addAll(TestOSGiUtil.configurePaxExamLogging());
|
||||
|
||||
options.add(CoreOptions.junitBundles());
|
||||
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-resources.xml"));
|
||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
|
||||
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
|
||||
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
|
||||
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
||||
|
||||
options.addAll(TestOSGiUtil.coreJettyDependencies());
|
||||
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
|
||||
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
|
||||
|
||||
//Note: we have to back down the version of bnd used here because tinybundles expects only this version
|
||||
options.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("bndlib").version("2.4.0").start());
|
||||
options.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").version("2.1.1").start());
|
||||
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-webapp-resources").type("war").versionAsInProject());
|
||||
options.add(CoreOptions.cleanCaches(true));
|
||||
return options.toArray(new Option[options.size()]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebInfResourceNotOnBundleClasspath() throws Exception
|
||||
{
|
||||
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
|
||||
TestOSGiUtil.diagnoseBundles(bundleContext);
|
||||
|
||||
//Test the test-jetty-osgi-webapp-resource bundle with a
|
||||
//Bundle-Classpath that does NOT include WEB-INF/classes
|
||||
HttpClient client = new HttpClient();
|
||||
try
|
||||
{
|
||||
client.start();
|
||||
|
||||
String port = System.getProperty("boot.resources.port");
|
||||
assertNotNull(port);
|
||||
ContentResponse response = client.GET("http://127.0.0.1:" + port + "/hello/a");
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
String content = response.getContentAsString();
|
||||
//check that fake.properties is only listed once from the classpath
|
||||
assertEquals(content.indexOf("fake.properties"), content.lastIndexOf("fake.properties"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebInfResourceOnBundleClasspath() throws Exception
|
||||
{
|
||||
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
|
||||
TestOSGiUtil.diagnoseBundles(bundleContext);
|
||||
|
||||
Bundle webappBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.webapp.resources");
|
||||
|
||||
//Make a new bundle based on the test-jetty-osgi-webapp-resources war bundle, but
|
||||
//change the Bundle-Classpath so that WEB-INF/classes IS on the bundle classpath
|
||||
File warFile = new File("target/test-jetty-osgi-webapp-resources.war");
|
||||
TinyBundle tiny = TinyBundles.bundle();
|
||||
tiny.read(new FileInputStream(warFile));
|
||||
tiny.set(Constants.BUNDLE_CLASSPATH, "., WEB-INF/classes/");
|
||||
tiny.set(Constants.BUNDLE_SYMBOLICNAME, "org.eclipse.jetty.osgi.webapp.resources.alt");
|
||||
InputStream is = tiny.build(TinyBundles.withBnd());
|
||||
bundleContext.installBundle("dummyAltLocation", is);
|
||||
|
||||
webappBundle.stop();
|
||||
Bundle bundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.webapp.resources.alt");
|
||||
bundle.start();
|
||||
|
||||
HttpClient client = new HttpClient();
|
||||
try
|
||||
{
|
||||
client.start();
|
||||
|
||||
String port = System.getProperty("boot.resources.port");
|
||||
assertNotNull(port);
|
||||
ContentResponse response = client.GET("http://127.0.0.1:" + port + "/hello/a");
|
||||
String content = response.getContentAsString();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
//check that fake.properties is only listed once from the classpath
|
||||
assertEquals(content.indexOf("fake.properties"), content.lastIndexOf("fake.properties"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.plus.webapp;
|
||||
|
||||
import java.util.Random;
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NameNotFoundException;
|
||||
|
@ -110,8 +109,7 @@ public class PlusConfiguration extends AbstractConfiguration
|
|||
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
|
||||
try
|
||||
{
|
||||
Random random = new Random();
|
||||
_key = random.nextInt();
|
||||
_key = (int)(this.hashCode() ^ System.nanoTime());
|
||||
Context context = new InitialContext();
|
||||
Context compCtx = (Context)context.lookup("java:comp");
|
||||
compCtx.addToEnvironment(NamingContext.LOCK_PROPERTY, _key);
|
||||
|
|
|
@ -28,6 +28,8 @@ import java.util.Locale;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
|
@ -49,6 +51,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.HttpCookieStore;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -466,6 +469,14 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
return HttpHeaderValue.CONTINUE.is(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
}
|
||||
|
||||
protected Request newProxyRequest(HttpServletRequest request, String rewrittenTarget)
|
||||
{
|
||||
return getHttpClient().newRequest(rewrittenTarget)
|
||||
.method(request.getMethod())
|
||||
.version(HttpVersion.fromString(request.getProtocol()))
|
||||
.attribute(CLIENT_REQUEST_ATTRIBUTE, request);
|
||||
}
|
||||
|
||||
protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
// First clear possibly existing headers, as we are going to copy those from the client request.
|
||||
|
@ -529,9 +540,50 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
addXForwardedHeaders(clientRequest, proxyRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the HTTP {@code Via} header to the proxied request.
|
||||
*
|
||||
* @param proxyRequest the request being proxied
|
||||
* @see #addViaHeader(HttpServletRequest, Request)
|
||||
*/
|
||||
protected void addViaHeader(Request proxyRequest)
|
||||
{
|
||||
proxyRequest.headers(headers -> headers.add(HttpHeader.VIA, "http/1.1 " + getViaHost()));
|
||||
HttpServletRequest clientRequest = (HttpServletRequest)proxyRequest.getAttributes().get(CLIENT_REQUEST_ATTRIBUTE);
|
||||
addViaHeader(clientRequest, proxyRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds the HTTP {@code Via} header to the proxied request, taking into account data present in the client request.</p>
|
||||
* <p>This method considers the protocol of the client request when forming the proxied request. If it
|
||||
* is HTTP, then the protocol name will not be included in the {@code Via} header that is sent by the proxy, and only
|
||||
* the protocol version will be sent. If it is not, the entire protocol (name and version) will be included.
|
||||
* If the client request includes a {@code Via} header, the result will be appended to that to form a chain.</p>
|
||||
*
|
||||
* @param clientRequest the client request
|
||||
* @param proxyRequest the request being proxied
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">RFC 7230 section 5.7.1</a>
|
||||
*/
|
||||
protected void addViaHeader(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
String protocol = clientRequest.getProtocol();
|
||||
String[] parts = protocol.split("/", 2);
|
||||
// Retain only the version if the protocol is HTTP.
|
||||
String protocolPart = parts.length == 2 && "HTTP".equalsIgnoreCase(parts[0]) ? parts[1] : protocol;
|
||||
String viaHeaderValue = protocolPart + " " + getViaHost();
|
||||
proxyRequest.headers(headers -> headers.computeField(HttpHeader.VIA, (header, viaFields) ->
|
||||
{
|
||||
if (viaFields == null || viaFields.isEmpty())
|
||||
return new HttpField(header, viaHeaderValue);
|
||||
String separator = ", ";
|
||||
String newValue = viaFields.stream()
|
||||
.flatMap(field -> Stream.of(field.getValues()))
|
||||
.filter(value -> !StringUtil.isBlank(value))
|
||||
.collect(Collectors.joining(separator));
|
||||
if (newValue.length() > 0)
|
||||
newValue += separator;
|
||||
newValue += viaHeaderValue;
|
||||
return new HttpField(HttpHeader.VIA, newValue);
|
||||
}));
|
||||
}
|
||||
|
||||
protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
|
|
|
@ -48,7 +48,6 @@ import org.eclipse.jetty.client.api.Response;
|
|||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
@ -93,9 +92,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
return;
|
||||
}
|
||||
|
||||
final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget)
|
||||
.method(clientRequest.getMethod())
|
||||
.version(HttpVersion.fromString(clientRequest.getProtocol()));
|
||||
Request proxyRequest = newProxyRequest(clientRequest, rewrittenTarget);
|
||||
|
||||
copyRequestHeaders(clientRequest, proxyRequest);
|
||||
|
||||
|
@ -118,7 +115,6 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
{
|
||||
// Must delay the call to request.getInputStream()
|
||||
// that sends the 100 Continue to the client.
|
||||
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, clientRequest);
|
||||
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
|
||||
{
|
||||
try
|
||||
|
|
|
@ -71,9 +71,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
return;
|
||||
}
|
||||
|
||||
Request proxyRequest = getHttpClient().newRequest(rewrittenTarget)
|
||||
.method(request.getMethod())
|
||||
.version(HttpVersion.fromString(request.getProtocol()));
|
||||
Request proxyRequest = newProxyRequest(request, rewrittenTarget);
|
||||
|
||||
copyRequestHeaders(request, proxyRequest);
|
||||
|
||||
|
@ -92,7 +90,6 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
// that sends the 100 Continue to the client.
|
||||
AsyncRequestContent delegate = new AsyncRequestContent();
|
||||
proxyRequest.body(delegate);
|
||||
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, request);
|
||||
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
|
||||
{
|
||||
try
|
||||
|
|
|
@ -75,7 +75,6 @@ import org.eclipse.jetty.util.Utf8StringBuilder;
|
|||
import org.eclipse.jetty.util.ajax.JSON;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -88,7 +87,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Disabled("See issue #3974")
|
||||
public class AsyncMiddleManServletTest
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncMiddleManServletTest.class);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
|
||||
public class EmptyServerHandler extends AbstractHandler
|
||||
{
|
||||
@Override
|
||||
public final void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
service(target, jettyRequest, request, response);
|
||||
}
|
||||
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
}
|
||||
}
|
|
@ -21,11 +21,15 @@ package org.eclipse.jetty.proxy;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
|
@ -35,10 +39,16 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.Net;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
|
@ -46,10 +56,14 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
|
@ -75,7 +89,7 @@ public class ForwardProxyServerTest
|
|||
private Server proxy;
|
||||
private ServerConnector proxyConnector;
|
||||
|
||||
protected void startServer(SslContextFactory.Server serverTLS, ConnectionFactory connectionFactory) throws Exception
|
||||
protected void startServer(SslContextFactory.Server serverTLS, ConnectionFactory connectionFactory, Handler handler) throws Exception
|
||||
{
|
||||
serverSslContextFactory = serverTLS;
|
||||
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||
|
@ -83,10 +97,11 @@ public class ForwardProxyServerTest
|
|||
server = new Server(serverThreads);
|
||||
serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory);
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
protected void startProxy() throws Exception
|
||||
protected void startProxy(ProxyServlet proxyServlet) throws Exception
|
||||
{
|
||||
QueuedThreadPool proxyThreads = new QueuedThreadPool();
|
||||
proxyThreads.setName("proxy");
|
||||
|
@ -100,7 +115,7 @@ public class ForwardProxyServerTest
|
|||
proxy.setHandler(connectHandler);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/");
|
||||
proxyHandler.addServlet(ProxyServlet.class, "/*");
|
||||
proxyHandler.addServlet(new ServletHolder(proxyServlet), "/*");
|
||||
|
||||
proxy.start();
|
||||
}
|
||||
|
@ -167,7 +182,7 @@ public class ForwardProxyServerTest
|
|||
// the client, and convert it to a relative URI.
|
||||
// The ConnectHandler won't modify what the client
|
||||
// sent, which must be a relative URI.
|
||||
assertThat(request.length(), Matchers.greaterThan(0));
|
||||
assertThat(request.length(), greaterThan(0));
|
||||
if (serverSslContextFactory == null)
|
||||
assertFalse(request.contains("http://"));
|
||||
else
|
||||
|
@ -187,8 +202,8 @@ public class ForwardProxyServerTest
|
|||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
}, new EmptyServerHandler());
|
||||
startProxy(new ProxyServlet());
|
||||
|
||||
SslContextFactory.Client clientTLS = new SslContextFactory.Client(true);
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
|
@ -212,4 +227,83 @@ public class ForwardProxyServerTest
|
|||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"::2", "[::3]"})
|
||||
public void testIPv6WithXForwardedForHeader(String ipv6) throws Exception
|
||||
{
|
||||
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
|
||||
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
|
||||
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||
startServer(null, http, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
String remoteHost = jettyRequest.getRemoteHost();
|
||||
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
|
||||
String remoteAddr = jettyRequest.getRemoteAddr();
|
||||
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
|
||||
}
|
||||
});
|
||||
startProxy(new ProxyServlet()
|
||||
{
|
||||
@Override
|
||||
protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
proxyRequest.headers(headers -> headers.put(HttpHeader.X_FORWARDED_FOR, ipv6));
|
||||
}
|
||||
});
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
|
||||
httpClient.start();
|
||||
|
||||
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
|
||||
.scheme("http")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6WithForwardedHeader() throws Exception
|
||||
{
|
||||
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
|
||||
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
|
||||
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||
startServer(null, http, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
String remoteHost = jettyRequest.getRemoteHost();
|
||||
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
|
||||
String remoteAddr = jettyRequest.getRemoteAddr();
|
||||
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
|
||||
}
|
||||
});
|
||||
startProxy(new ProxyServlet()
|
||||
{
|
||||
@Override
|
||||
protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
proxyRequest.headers(headers -> headers.put(HttpHeader.FORWARDED, "for=\"[::2]\""));
|
||||
}
|
||||
});
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
|
||||
httpClient.start();
|
||||
|
||||
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
|
||||
.scheme("http")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,11 +62,13 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.Net;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -475,6 +477,36 @@ public class ForwardProxyTLSServerTest
|
|||
httpClient.stop();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("proxyTLS")
|
||||
public void testIPv6(SslContextFactory.Server proxyTLS) throws Exception
|
||||
{
|
||||
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
|
||||
|
||||
startTLSServer(new ServerHandler());
|
||||
startProxy(proxyTLS);
|
||||
|
||||
HttpClient httpClient = newHttpClient();
|
||||
HttpProxy httpProxy = new HttpProxy(new Origin.Address("[::1]", proxyConnector.getLocalPort()), proxyTLS != null);
|
||||
httpClient.getProxyConfiguration().getProxies().add(httpProxy);
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
{
|
||||
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
|
||||
.scheme(HttpScheme.HTTPS.asString())
|
||||
.path("/echo?body=")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("proxyTLS")
|
||||
public void testProxyAuthentication(SslContextFactory.Server proxyTLS) throws Exception
|
||||
|
|
|
@ -60,6 +60,7 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.ConnectionPool;
|
||||
|
@ -89,15 +90,18 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.containsHeader;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -147,6 +151,11 @@ public class ProxyServletTest
|
|||
}
|
||||
|
||||
private void startProxy(Class<? extends ProxyServlet> proxyServletClass, Map<String, String> initParams) throws Exception
|
||||
{
|
||||
startProxy(proxyServletClass.getConstructor().newInstance(), initParams);
|
||||
}
|
||||
|
||||
private void startProxy(AbstractProxyServlet proxyServlet, Map<String, String> initParams) throws Exception
|
||||
{
|
||||
QueuedThreadPool proxyPool = new QueuedThreadPool();
|
||||
proxyPool.setName("proxy");
|
||||
|
@ -161,9 +170,8 @@ public class ProxyServletTest
|
|||
proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
proxyServlet = proxyServletClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
proxyContext = new ServletContextHandler(proxy, "/", true, false);
|
||||
this.proxyServlet = proxyServlet;
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyServletHolder.setInitParameters(initParams);
|
||||
proxyContext.addServlet(proxyServletHolder, "/*");
|
||||
|
@ -554,7 +562,98 @@ public class ProxyServletTest
|
|||
ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort());
|
||||
assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",
|
||||
response.getContentAsString(),
|
||||
Matchers.equalTo("localhost:" + serverConnector.getLocalPort()));
|
||||
equalTo("localhost:" + serverConnector.getLocalPort()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testProxyViaHeaderIsAdded(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
startServer(new EmptyHttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
PrintWriter writer = response.getWriter();
|
||||
List<String> viaValues = Collections.list(request.getHeaders("Via"));
|
||||
writer.write(String.join(", ", viaValues));
|
||||
}
|
||||
});
|
||||
String viaHost = "my-good-via-host.example.org";
|
||||
startProxy(proxyServletClass, Collections.singletonMap("viaHost", viaHost));
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort());
|
||||
assertThat(response.getContentAsString(), equalTo("1.1 " + viaHost));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("impls")
|
||||
public void testProxyViaHeaderValueIsAppended(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
startServer(new EmptyHttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Make sure the proxy coalesced the Via headers into just one.
|
||||
org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request)request;
|
||||
assertEquals(1, jettyRequest.getHttpFields().getFields(HttpHeader.VIA).size());
|
||||
PrintWriter writer = response.getWriter();
|
||||
List<String> viaValues = Collections.list(request.getHeaders("Via"));
|
||||
writer.write(String.join(", ", viaValues));
|
||||
}
|
||||
});
|
||||
String viaHost = "beatrix";
|
||||
startProxy(proxyServletClass, Collections.singletonMap("viaHost", viaHost));
|
||||
startClient();
|
||||
|
||||
String existingViaHeader = "1.0 charon";
|
||||
ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort())
|
||||
.header(HttpHeader.VIA, existingViaHeader)
|
||||
.send();
|
||||
String expected = String.join(", ", existingViaHeader, "1.1 " + viaHost);
|
||||
assertThat(response.getContentAsString(), equalTo(expected));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"HTTP/2.0", "FCGI/1.0"})
|
||||
public void testViaHeaderProtocols(String protocol) throws Exception
|
||||
{
|
||||
startServer(new EmptyHttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
PrintWriter writer = response.getWriter();
|
||||
List<String> viaValues = Collections.list(request.getHeaders("Via"));
|
||||
writer.write(String.join(", ", viaValues));
|
||||
}
|
||||
});
|
||||
String viaHost = "proxy";
|
||||
startProxy(new ProxyServlet()
|
||||
{
|
||||
@Override
|
||||
protected void addViaHeader(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
HttpServletRequest wrapped = new HttpServletRequestWrapper(clientRequest)
|
||||
{
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
};
|
||||
super.addViaHeader(wrapped, proxyRequest);
|
||||
}
|
||||
}, Collections.singletonMap("viaHost", viaHost));
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort());
|
||||
|
||||
String expectedProtocol = protocol.startsWith("HTTP/") ? protocol.substring("HTTP/".length()) : protocol;
|
||||
String expected = expectedProtocol + " " + viaHost;
|
||||
assertThat(response.getContentAsString(), equalTo(expected));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -948,7 +1047,7 @@ public class ProxyServletTest
|
|||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
assertThat(response.getStatus(), Matchers.greaterThanOrEqualTo(500));
|
||||
assertThat(response.getStatus(), greaterThanOrEqualTo(500));
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
|
@ -1082,7 +1181,7 @@ public class ProxyServletTest
|
|||
// Make sure there is error page content, as the proxy-to-client response has been reset.
|
||||
InputStream input = listener.getInputStream();
|
||||
String body = IO.toString(input);
|
||||
assertThat(body, Matchers.containsString("HTTP ERROR 504"));
|
||||
assertThat(body, containsString("HTTP ERROR 504"));
|
||||
chunk1Latch.countDown();
|
||||
|
||||
// Result succeeds because a 504 is a valid HTTP response.
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.eclipse.jetty.util.QuotedStringTokenizer;
|
|||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -282,6 +283,7 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
|
||||
private static class Nonce
|
||||
{
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
final String _nonce;
|
||||
final long _ts;
|
||||
final BitSet _seen;
|
||||
|
@ -295,7 +297,7 @@ public class DigestAuthenticator extends LoginAuthenticator
|
|||
|
||||
public boolean seen(int count)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (count >= _seen.size())
|
||||
return true;
|
||||
|
|
|
@ -325,8 +325,10 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
return false;
|
||||
|
||||
for (Thread a : _acceptors)
|
||||
{
|
||||
if (a != null)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -446,7 +448,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
|
||||
public void setAccepting(boolean accepting)
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_accepting = accepting;
|
||||
_setAccepting.signalAll();
|
||||
|
@ -702,13 +704,16 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
if (_acceptorPriorityDelta != 0)
|
||||
thread.setPriority(Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, priority + _acceptorPriorityDelta)));
|
||||
|
||||
_acceptors[_id] = thread;
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_acceptors[_id] = thread;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (isRunning() && !_shutdown.isShutdown())
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (!_accepting && isRunning())
|
||||
{
|
||||
|
@ -738,7 +743,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
if (_acceptorPriorityDelta != 0)
|
||||
thread.setPriority(priority);
|
||||
|
||||
synchronized (AbstractConnector.this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_acceptors[_id] = null;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.eclipse.jetty.util.annotation.Name;
|
|||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Container;
|
||||
import org.eclipse.jetty.util.statistic.RateStatistic;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -64,6 +65,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AcceptRateLimit.class);
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final Server _server;
|
||||
private final List<AbstractConnector> _connectors = new ArrayList<>();
|
||||
private final Rate _rate;
|
||||
|
@ -123,7 +125,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
@ManagedOperation(value = "Resets the accept rate", impact = "ACTION")
|
||||
public void reset()
|
||||
{
|
||||
synchronized (_rate)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_rate.reset();
|
||||
if (_limiting)
|
||||
|
@ -142,7 +144,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
synchronized (_rate)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
|
@ -156,7 +158,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("AcceptLimit accept<{} rate<{} in {} for {}", _acceptRateLimit, _rate, _connectors);
|
||||
LOG.debug("AcceptLimit accept<{} rate<{} in {}", _acceptRateLimit, _rate, _connectors);
|
||||
|
||||
for (AbstractConnector c : _connectors)
|
||||
{
|
||||
|
@ -168,7 +170,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
synchronized (_rate)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_task != null)
|
||||
_task.cancel();
|
||||
|
@ -203,13 +205,11 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
@Override
|
||||
public void onAccepting(SelectableChannel channel)
|
||||
{
|
||||
synchronized (_rate)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
int rate = _rate.record();
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("onAccepting rate {}/{} for {} {}", rate, _acceptRateLimit, _rate, channel);
|
||||
}
|
||||
if (rate > _acceptRateLimit)
|
||||
{
|
||||
if (!_limiting)
|
||||
|
@ -238,7 +238,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
@Override
|
||||
public void run()
|
||||
{
|
||||
synchronized (_rate)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_task = null;
|
||||
if (!isRunning())
|
||||
|
@ -258,7 +258,7 @@ public class AcceptRateLimit extends AbstractLifeCycle implements SelectorManage
|
|||
}
|
||||
}
|
||||
|
||||
private final class Rate extends RateStatistic
|
||||
private static final class Rate extends RateStatistic
|
||||
{
|
||||
private Rate(long period, TimeUnit units)
|
||||
{
|
||||
|
|
|
@ -92,7 +92,7 @@ public class AsyncRequestLogWriter extends RequestLogWriter
|
|||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void doStart() throws Exception
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
super.doStart();
|
||||
_thread = new AsyncRequestLogWriter.WriterThread();
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
|
|||
import org.eclipse.jetty.util.annotation.Name;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Container;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -62,6 +63,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConnectionLimit.class);
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final Server _server;
|
||||
private final List<AbstractConnector> _connectors = new ArrayList<>();
|
||||
private final Set<SelectableChannel> _accepting = new HashSet<>();
|
||||
|
@ -108,7 +110,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@ManagedAttribute("The maximum number of connections allowed")
|
||||
public int getMaxConnections()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _maxConnections;
|
||||
}
|
||||
|
@ -116,7 +118,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
|
||||
public void setMaxConnections(int max)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_maxConnections = max;
|
||||
}
|
||||
|
@ -125,7 +127,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@ManagedAttribute("The current number of connections ")
|
||||
public int getConnections()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _connections;
|
||||
}
|
||||
|
@ -134,7 +136,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
|
@ -160,7 +162,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
for (AbstractConnector c : _connectors)
|
||||
{
|
||||
|
@ -229,7 +231,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
public void onAccepting(SelectableChannel channel)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_accepting.add(channel);
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -241,7 +243,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
public void onAcceptFailed(SelectableChannel channel, Throwable cause)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_accepting.remove(channel);
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -258,7 +260,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
public void onOpened(Connection connection)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_accepting.remove(connection.getEndPoint().getTransport());
|
||||
_connections++;
|
||||
|
@ -271,7 +273,7 @@ public class ConnectionLimit extends AbstractLifeCycle implements Listener, Sele
|
|||
@Override
|
||||
public void onClosed(Connection connection)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_connections--;
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -408,7 +408,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void doStart() throws Exception
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (_ignorePaths != null && _ignorePaths.length > 0)
|
||||
{
|
||||
|
|
|
@ -410,7 +410,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
case INCLUDE_CONTEXT_PATH:
|
||||
{
|
||||
ContextHandler.Context context = _baseRequest.getContext();
|
||||
return context == null ? null : context.getContextHandler().getContextPathEncoded();
|
||||
return context == null ? null : context.getContextHandler().getRequestContextPath();
|
||||
}
|
||||
case INCLUDE_QUERY_STRING:
|
||||
return _query;
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.jetty.io.QuietException;
|
|||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -144,6 +145,7 @@ public class HttpChannelState
|
|||
WAIT, // Wait for further events
|
||||
}
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private final HttpChannel _channel;
|
||||
private List<AsyncListener> _asyncListeners;
|
||||
private State _state = State.IDLE;
|
||||
|
@ -161,9 +163,14 @@ public class HttpChannelState
|
|||
_channel = channel;
|
||||
}
|
||||
|
||||
AutoLock lock()
|
||||
{
|
||||
return _lock.lock();
|
||||
}
|
||||
|
||||
public State getState()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
@ -171,7 +178,7 @@ public class HttpChannelState
|
|||
|
||||
public void addListener(AsyncListener listener)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (_asyncListeners == null)
|
||||
_asyncListeners = new ArrayList<>();
|
||||
|
@ -181,7 +188,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean hasListener(AsyncListener listener)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock ignored = lock())
|
||||
{
|
||||
if (_asyncListeners == null)
|
||||
return false;
|
||||
|
@ -200,7 +207,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isSendError()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _sendError;
|
||||
}
|
||||
|
@ -208,7 +215,7 @@ public class HttpChannelState
|
|||
|
||||
public void setTimeout(long ms)
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
_timeoutMs = ms;
|
||||
}
|
||||
|
@ -216,7 +223,7 @@ public class HttpChannelState
|
|||
|
||||
public long getTimeout()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _timeoutMs;
|
||||
}
|
||||
|
@ -224,7 +231,7 @@ public class HttpChannelState
|
|||
|
||||
public AsyncContextEvent getAsyncContextEvent()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _event;
|
||||
}
|
||||
|
@ -233,7 +240,7 @@ public class HttpChannelState
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return toStringLocked();
|
||||
}
|
||||
|
@ -262,7 +269,7 @@ public class HttpChannelState
|
|||
|
||||
public String getStatusString()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return getStatusStringLocked();
|
||||
}
|
||||
|
@ -270,7 +277,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean commitResponse()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
switch (_outputState)
|
||||
{
|
||||
|
@ -286,7 +293,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean partialResponse()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
switch (_outputState)
|
||||
{
|
||||
|
@ -302,7 +309,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean completeResponse()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
switch (_outputState)
|
||||
{
|
||||
|
@ -319,7 +326,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isResponseCommitted()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
switch (_outputState)
|
||||
{
|
||||
|
@ -333,7 +340,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isResponseCompleted()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _outputState == OutputState.COMPLETED;
|
||||
}
|
||||
|
@ -341,7 +348,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean abortResponse()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
switch (_outputState)
|
||||
{
|
||||
|
@ -365,7 +372,7 @@ public class HttpChannelState
|
|||
*/
|
||||
public Action handling()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("handling {}", toStringLocked());
|
||||
|
@ -407,7 +414,7 @@ public class HttpChannelState
|
|||
*/
|
||||
protected Action unhandle()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("unhandle {}", toStringLocked());
|
||||
|
@ -526,7 +533,7 @@ public class HttpChannelState
|
|||
{
|
||||
final List<AsyncListener> lastAsyncListeners;
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("startAsync {}", toStringLocked());
|
||||
|
@ -575,7 +582,7 @@ public class HttpChannelState
|
|||
{
|
||||
boolean dispatch = false;
|
||||
AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("dispatch {} -> {}", toStringLocked(), path);
|
||||
|
@ -611,7 +618,7 @@ public class HttpChannelState
|
|||
protected void timeout()
|
||||
{
|
||||
boolean dispatch = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Timeout {}", toStringLocked());
|
||||
|
@ -639,7 +646,7 @@ public class HttpChannelState
|
|||
{
|
||||
final List<AsyncListener> listeners;
|
||||
AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onTimeout {}", toStringLocked());
|
||||
|
@ -687,7 +694,7 @@ public class HttpChannelState
|
|||
{
|
||||
boolean handle = false;
|
||||
AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("complete {}", toStringLocked());
|
||||
|
@ -725,7 +732,7 @@ public class HttpChannelState
|
|||
// actually handled by #thrownException
|
||||
|
||||
AsyncContextEvent event = null;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("asyncError " + toStringLocked(), failure);
|
||||
|
@ -756,7 +763,7 @@ public class HttpChannelState
|
|||
{
|
||||
final AsyncContextEvent asyncEvent;
|
||||
final List<AsyncListener> asyncListeners;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("thrownException " + getStatusStringLocked(), th);
|
||||
|
@ -820,7 +827,7 @@ public class HttpChannelState
|
|||
});
|
||||
|
||||
// check the actions of the listeners
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (_requestState == RequestState.ASYNC && !_sendError)
|
||||
{
|
||||
|
@ -892,7 +899,7 @@ public class HttpChannelState
|
|||
if (message == null)
|
||||
message = HttpStatus.getMessage(code);
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("sendError {}", toStringLocked());
|
||||
|
@ -933,7 +940,7 @@ public class HttpChannelState
|
|||
|
||||
protected void completing()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("completing {}", toStringLocked());
|
||||
|
@ -954,7 +961,7 @@ public class HttpChannelState
|
|||
final AsyncContextEvent event;
|
||||
boolean handle = false;
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("completed {}", toStringLocked());
|
||||
|
@ -1008,7 +1015,7 @@ public class HttpChannelState
|
|||
}
|
||||
event.completed();
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
_requestState = RequestState.COMPLETED;
|
||||
if (_state == State.WAITING)
|
||||
|
@ -1026,7 +1033,7 @@ public class HttpChannelState
|
|||
protected void recycle()
|
||||
{
|
||||
cancelTimeout();
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("recycle {}", toStringLocked());
|
||||
|
@ -1055,7 +1062,7 @@ public class HttpChannelState
|
|||
public void upgrade()
|
||||
{
|
||||
cancelTimeout();
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("upgrade {}", toStringLocked());
|
||||
|
@ -1085,12 +1092,7 @@ public class HttpChannelState
|
|||
|
||||
protected void cancelTimeout()
|
||||
{
|
||||
final AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
{
|
||||
event = _event;
|
||||
}
|
||||
cancelTimeout(event);
|
||||
cancelTimeout(getAsyncContextEvent());
|
||||
}
|
||||
|
||||
protected void cancelTimeout(AsyncContextEvent event)
|
||||
|
@ -1101,7 +1103,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isIdle()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _state == State.IDLE;
|
||||
}
|
||||
|
@ -1109,7 +1111,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isExpired()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
// TODO review
|
||||
return _requestState == RequestState.EXPIRE || _requestState == RequestState.EXPIRING;
|
||||
|
@ -1118,7 +1120,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isInitial()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _initial;
|
||||
}
|
||||
|
@ -1126,7 +1128,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isSuspended()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _state == State.WAITING || _state == State.HANDLING && _requestState == RequestState.ASYNC;
|
||||
}
|
||||
|
@ -1134,7 +1136,7 @@ public class HttpChannelState
|
|||
|
||||
boolean isCompleted()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return _requestState == RequestState.COMPLETED;
|
||||
}
|
||||
|
@ -1142,7 +1144,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isAsyncStarted()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (_state == State.HANDLING)
|
||||
return _requestState != RequestState.BLOCKING;
|
||||
|
@ -1152,7 +1154,7 @@ public class HttpChannelState
|
|||
|
||||
public boolean isAsync()
|
||||
{
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
return !_initial || _requestState != RequestState.BLOCKING;
|
||||
}
|
||||
|
@ -1170,12 +1172,7 @@ public class HttpChannelState
|
|||
|
||||
public ContextHandler getContextHandler()
|
||||
{
|
||||
final AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
{
|
||||
event = _event;
|
||||
}
|
||||
return getContextHandler(event);
|
||||
return getContextHandler(getAsyncContextEvent());
|
||||
}
|
||||
|
||||
ContextHandler getContextHandler(AsyncContextEvent event)
|
||||
|
@ -1191,12 +1188,7 @@ public class HttpChannelState
|
|||
|
||||
public ServletResponse getServletResponse()
|
||||
{
|
||||
final AsyncContextEvent event;
|
||||
synchronized (this)
|
||||
{
|
||||
event = _event;
|
||||
}
|
||||
return getServletResponse(event);
|
||||
return getServletResponse(getAsyncContextEvent());
|
||||
}
|
||||
|
||||
public ServletResponse getServletResponse(AsyncContextEvent event)
|
||||
|
@ -1240,7 +1232,7 @@ public class HttpChannelState
|
|||
public void onReadUnready()
|
||||
{
|
||||
boolean interested = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onReadUnready {}", toStringLocked());
|
||||
|
@ -1286,7 +1278,7 @@ public class HttpChannelState
|
|||
public boolean onContentAdded()
|
||||
{
|
||||
boolean woken = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onContentAdded {}", toStringLocked());
|
||||
|
@ -1329,7 +1321,7 @@ public class HttpChannelState
|
|||
public boolean onReadReady()
|
||||
{
|
||||
boolean woken = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onReadReady {}", toStringLocked());
|
||||
|
@ -1362,7 +1354,7 @@ public class HttpChannelState
|
|||
public boolean onReadPossible()
|
||||
{
|
||||
boolean woken = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onReadPossible {}", toStringLocked());
|
||||
|
@ -1394,7 +1386,7 @@ public class HttpChannelState
|
|||
public boolean onReadEof()
|
||||
{
|
||||
boolean woken = false;
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onEof {}", toStringLocked());
|
||||
|
@ -1414,7 +1406,7 @@ public class HttpChannelState
|
|||
{
|
||||
boolean wake = false;
|
||||
|
||||
synchronized (this)
|
||||
try (AutoLock l = lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onWritePossible {}", toStringLocked());
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.jetty.io.RuntimeIOException;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -125,6 +126,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
static final Content EOF_CONTENT = new EofContent("EOF");
|
||||
static final Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
|
||||
|
||||
private final AutoLock.WithCondition _lock = new AutoLock.WithCondition();
|
||||
private final byte[] _oneByteBuffer = new byte[1];
|
||||
private Content _content;
|
||||
private Content _intercepted;
|
||||
|
@ -151,7 +153,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public void recycle()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_content != null)
|
||||
_content.failed(null);
|
||||
|
@ -212,7 +214,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
{
|
||||
int available = 0;
|
||||
boolean woken = false;
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_content == null)
|
||||
_content = _inputQ.poll();
|
||||
|
@ -259,8 +261,8 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
public int read(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
boolean wake = false;
|
||||
int l;
|
||||
synchronized (_inputQ)
|
||||
int read;
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
// Calculate minimum request rate for DOS protection
|
||||
long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate();
|
||||
|
@ -287,9 +289,9 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
Content item = nextContent();
|
||||
if (item != null)
|
||||
{
|
||||
l = get(item, b, off, len);
|
||||
read = get(item, b, off, len);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} read {} from {}", this, l, item);
|
||||
LOG.debug("{} read {} from {}", this, read, item);
|
||||
|
||||
// Consume any following poison pills
|
||||
if (item.isEmpty())
|
||||
|
@ -301,9 +303,9 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
if (!_state.blockForContent(this))
|
||||
{
|
||||
// Not blocking, so what should we return?
|
||||
l = _state.noContent();
|
||||
read = _state.noContent();
|
||||
|
||||
if (l < 0)
|
||||
if (read < 0)
|
||||
// If EOF do we need to wake for allDataRead callback?
|
||||
wake = _channelState.onReadEof();
|
||||
break;
|
||||
|
@ -313,7 +315,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
if (wake)
|
||||
wake();
|
||||
return l;
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,7 +335,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
*/
|
||||
public void asyncReadProduce() throws IOException
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
produceContent();
|
||||
}
|
||||
|
@ -522,6 +524,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
*/
|
||||
protected void blockForContent() throws IOException
|
||||
{
|
||||
assert _lock.isHeldByCurrentThread();
|
||||
try
|
||||
{
|
||||
_waitingForContent = true;
|
||||
|
@ -539,9 +542,9 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} blocking for content timeout={}", this, timeout);
|
||||
if (timeout > 0)
|
||||
_inputQ.wait(timeout);
|
||||
_lock.await(timeout, TimeUnit.MILLISECONDS);
|
||||
else
|
||||
_inputQ.wait();
|
||||
_lock.await();
|
||||
|
||||
loop = true;
|
||||
}
|
||||
|
@ -560,7 +563,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
*/
|
||||
public boolean addContent(Content content)
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_waitingForContent = false;
|
||||
if (_firstByteTimeStamp == -1)
|
||||
|
@ -594,7 +597,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean hasContent()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _content != null || _inputQ.size() > 0;
|
||||
}
|
||||
|
@ -602,15 +605,15 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public void unblock()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock.WithCondition l = _lock.lock())
|
||||
{
|
||||
_inputQ.notify();
|
||||
l.signal();
|
||||
}
|
||||
}
|
||||
|
||||
public long getContentConsumed()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _contentConsumed;
|
||||
}
|
||||
|
@ -640,7 +643,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean consumeAll()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -669,7 +672,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean isError()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _state instanceof ErrorState;
|
||||
}
|
||||
|
@ -677,7 +680,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean isAsync()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _state == ASYNC;
|
||||
}
|
||||
|
@ -686,7 +689,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
@Override
|
||||
public boolean isFinished()
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _state instanceof EOFState;
|
||||
}
|
||||
|
@ -697,7 +700,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
{
|
||||
try
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_listener == null)
|
||||
return true;
|
||||
|
@ -725,7 +728,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
boolean woken = false;
|
||||
try
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_listener != null)
|
||||
throw new IllegalStateException("ReadListener already set");
|
||||
|
@ -773,7 +776,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean onIdleTimeout(Throwable x)
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
boolean neverDispatched = getHttpChannelState().isIdle();
|
||||
if ((_waitingForContent || neverDispatched) && !isError())
|
||||
|
@ -788,7 +791,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
public boolean failed(Throwable x)
|
||||
{
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
// Errors may be reported multiple times, for example
|
||||
// a local idle timeout and a remote I/O failure.
|
||||
|
@ -816,9 +819,10 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
|
||||
private boolean wakeup()
|
||||
{
|
||||
assert _lock.isHeldByCurrentThread();
|
||||
if (_listener != null)
|
||||
return _channelState.onContentAdded();
|
||||
_inputQ.notify();
|
||||
_lock.signal();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -833,7 +837,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
Throwable error;
|
||||
boolean aeof = false;
|
||||
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
listener = _listener;
|
||||
|
||||
|
@ -922,7 +926,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
long consumed;
|
||||
int q;
|
||||
Content content;
|
||||
synchronized (_inputQ)
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
state = _state;
|
||||
consumed = _contentConsumed;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue